pax_global_header00006660000000000000000000000064147272770600014526gustar00rootroot0000000000000052 comment=05bd983473e021839305e56cc6ffc8ad3ac24e0b fastnetmon-1.2.8/000077500000000000000000000000001472727706000137145ustar00rootroot00000000000000fastnetmon-1.2.8/.circleci/000077500000000000000000000000001472727706000155475ustar00rootroot00000000000000fastnetmon-1.2.8/.circleci/config.yml000066400000000000000000000575651472727706000175610ustar00rootroot00000000000000version: 2.1 parameters: fastnetmon_build_version: type: string default: "1.2.8" orbs: win: circleci/windows@4.1 jobs: build_windows: parameters: windows_name: type: string default_shell: type: string default: "c:/tools/msys64/msys2_shell.cmd -defterm -no-start -mingw64 -full-path -here" executor: << parameters.windows_name >> steps: - checkout - run: 'Write-Host "Hello from FastNetMon"' - run: choco install -y --no-progress cmake --installargs "ADD_CMAKE_TO_PATH=User" - run: choco install -y --no-progress msys2 - run: mkdir src/build - run: name: Install dependency libraries shell: c:/tools/msys64/msys2_shell.cmd -defterm -no-start -msys2 -full-path -here -c command: pacman -S --needed --noconfirm make mingw-w64-x86_64-gcc mingw-w64-x86_64-make mingw-w64-x86_64-boost mingw-w64-x86_64-cmake zip unzip mingw-w64-x86_64-capnproto mingw-w64-x86_64-grpc mingw-w64-x86_64-openssl mingw-w64-x86_64-hiredis mingw-w64-x86_64-librdkafka mingw-w64-x86_64-protobuf mingw-w64-x86_64-ncurses mingw-w64-x86_64-libpcap - run: name: Download log4cpp shell: << parameters.default_shell >> command: wget https://deac-riga.dl.sourceforge.net/project/log4cpp/log4cpp-1.1.x%20%28new%29/log4cpp-1.1/log4cpp-1.1.4.tar.gz - run: name: Unpack log4cpp shell: << parameters.default_shell >> command: tar -xf log4cpp-1.1.4.tar.gz - run: name: Patch log4cpp to compile it on msys2 shell: << parameters.default_shell >> command: sed -i '/#define int64_t __int64/d' log4cpp/include/log4cpp/config-MinGW32.h - run: name: Patch tests shell: << parameters.default_shell >> command: sed -i 's/typedef int64_t usec_t;/#include \ntypedef int64_t usec_t;/' log4cpp/tests/Clock.hh - run: name: Configure log4cpp shell: << parameters.default_shell >> command: cd log4cpp && ./configure - run: name: Build log4cpp shell: << parameters.default_shell >> command: cd log4cpp && make -j - run: name: Install log4cpp shell: << parameters.default_shell >> command: cd log4cpp && make install - run: name: Run cmake shell: << parameters.default_shell >> command: cmake -DENABLE_MONGODB_SUPPORT=FALSE -DENABLE_PCAP_SUPPORT=FALSE -DLINK_WITH_ABSL=TRUE -S src -B src/build - run: name: Build shell: << parameters.default_shell >> command: cd src/build && ninja build_macos: macos: xcode: 13.4.1 environment: # We need it to address Error: No head is defined for fastnetmon # https://github.com/Homebrew/discussions/discussions/4136 HOMEBREW_NO_INSTALL_FROM_API: 1 steps: - run: env - checkout - run: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - run: cp src/packaging/homebrew/fastnetmon.rb /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/f/fastnetmon.rb - run: brew install --build-from-source --HEAD --verbose --debug fastnetmon build_debian_upstream_package: machine: image: ubuntu-2204:current resource_class: large parameters: docker_image: type: string debian_codename: type: string steps: - run: name: Create folder to share data between host and Docker container with relaxed permissions to allow use of save / restore cache logic command: sudo mkdir /data; sudo chmod 777 /data - run: name: Docker with priviledged mode to run chroot inside and we use tail -f to keep container running command: sudo docker run -d -v /sys/fs/cgroup/:/sys/fs/cgroup:ro -v /data:/data:rw --privileged --cap-add SYS_ADMIN --name linux_priviledged_container << parameters.docker_image >> tail -f /dev/null - run: sudo docker exec -it linux_priviledged_container apt-get update; true - run: name: Explicitly specify mirror to avoid pbuilder failure on configuration step command: echo "MIRRORSITE=http://http.us.debian.org/debian"| sudo docker exec -i linux_priviledged_container tee /etc/pbuilderrc - run: sudo docker exec -it linux_priviledged_container cat /etc/pbuilderrc - run: sudo docker exec -it linux_priviledged_container apt install -y dpkg-dev git pbuilder - run: sudo docker exec -it linux_priviledged_container git clone https://github.com/pavel-odintsov/fastnetmon - run: sudo docker exec -it linux_priviledged_container git clone https://salsa.debian.org/debian/fastnetmon.git fastnetmon-debian-salsa - run: sudo docker exec -it linux_priviledged_container rm -f fastnetmon-debian-salsa/debian/patches/series - run: sudo docker exec -it linux_priviledged_container tar -czf fastnetmon_$(sudo docker exec -it linux_priviledged_container head -n 1 fastnetmon-debian-salsa/debian/changelog|awk '{print $2}'|sed 's/[()]//g' | sed -E 's/(\-[0-9]+)?$//').orig.tar.gz fastnetmon - run: sudo docker exec -it linux_priviledged_container ls -la - run: sudo docker exec -it linux_priviledged_container bash -c "cd fastnetmon && rm -rf debian && cp -a ../fastnetmon-debian-salsa/debian/ . && dpkg-buildpackage -S -sa -d" - run: name: List produced source files command: sudo docker exec -it linux_priviledged_container ls -la - run: name: Show content of data folder and permissions for it command: ls -la /data - run: name: Check that we have anything in data folder on VM command: ls -la /data - run: name: "Run pbuilder run Docker if we have no image in place" command: "sudo docker exec -it linux_priviledged_container pbuilder --create --basetgz /data/debian_base.tgz --distribution << parameters.debian_codename >>" - run: ls -la /data - run: sudo docker exec -it linux_priviledged_container pbuilder --build --basetgz /data/debian_base.tgz --debbuildopts "-sa" /fastnetmon_$(sudo docker exec -it linux_priviledged_container head -n 1 fastnetmon-debian-salsa/debian/changelog|awk '{print $2}'|sed 's/[()]//g').dsc build_docker: machine: image: ubuntu-2204:current steps: - checkout - run: name: Extract GitHub Username command: | GH_USERNAME=$(echo "<< pipeline.project.git_url >>" | sed -n 's#.*/\([^/]*\)/.*#\1#p') echo "GitHub username is $GH_USERNAME" echo "export GH_USERNAME=$GH_USERNAME" >> $BASH_ENV - run: name: Build Docker images command: | echo $CR_PAT | docker login ghcr.io -u $GH_USERNAME --password-stdin docker run --rm --privileged multiarch/qemu-user-static --reset -p yes docker buildx create --use docker buildx inspect --bootstrap docker buildx build \ --file src/Dockerfile \ --platform linux/amd64,linux/arm64 \ --tag ghcr.io/$GH_USERNAME/fastnetmon-community:<< pipeline.parameters.fastnetmon_build_version >> \ --tag ghcr.io/$GH_USERNAME/fastnetmon-community:latest \ --push . build_gce: machine: # We use this image because it uses GCE instead of AWS for testing # https://circleci.com/blog/building-android-on-circleci/ # You can find latest tag here: https://circleci.com/developer/images/image/cimg/android#image-tags image: android:2022.09.1 resource_class: large steps: - checkout build_fedora_upstream: parameters: docker_image: type: string docker: - image: << parameters.docker_image >> resource_class: large steps: - checkout - run: dnf install -y rpm-build rpmdevtools dnf-plugins-core - run: mkdir -p ~/rpmbuild/SPECS - run: cp src/packaging/fedora/fastnetmon.spec ~/rpmbuild/SPECS - run: name: Install build dependencies command: dnf builddep -y ~/rpmbuild/SPECS/fastnetmon.spec - run: name: Download source command: cd ~/rpmbuild && spectool -g -R SPECS/fastnetmon.spec - run: name: Added sysusers file to SOURCES command: cp src/packaging/fedora/fastnetmon.sysusers ~/rpmbuild/SOURCES - run: name: Build source RPM command: cd ~/rpmbuild/SPECS && rpmbuild -bs fastnetmon.spec - store_artifacts: path: /root/rpmbuild/SRPMS - run: name: Build RPM command: cd ~/rpmbuild/SPECS && rpmbuild -bb fastnetmon.spec - store_artifacts: path: /root/rpmbuild/RPMS/x86_64 build_epel9_upstream: docker: - image: almalinux:9 resource_class: large steps: - checkout - run: dnf install -y rpm-build rpmdevtools dnf-plugins-core - run: dnf install -y dnf-plugins-core - run: dnf config-manager --set-enabled crb - run: dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm - run: mkdir -p ~/rpmbuild/SPECS # It's copy of Fedora SPEC file with capnproto disabled because we have no package for it in EPEL: https://src.fedoraproject.org/rpms/capnproto - run: cp src/packaging/epel/fastnetmon.spec ~/rpmbuild/SPECS - run: name: Install build dependencies command: dnf builddep -y ~/rpmbuild/SPECS/fastnetmon.spec - run: name: Download source command: cd ~/rpmbuild && spectool -g -R SPECS/fastnetmon.spec - run: name: Added sysusers file to SOURCES command: cp src/packaging/fedora/fastnetmon.sysusers ~/rpmbuild/SOURCES - run: name: Build source RPM command: cd ~/rpmbuild/SPECS && rpmbuild -bs fastnetmon.spec - store_artifacts: path: /root/rpmbuild/SRPMS - run: name: Build RPM command: cd ~/rpmbuild/SPECS && rpmbuild -bb fastnetmon.spec - store_artifacts: path: /root/rpmbuild/RPMS/x86_64 build_debian_system_dependencies: parameters: docker_image: type: string resource_class: type: string default: large machine: image: ubuntu-2204:current environment: DEBIAN_FRONTEND: noninteractive resource_class: << parameters.resource_class >> steps: - run: sudo docker run --name linux_docker -d -t << parameters.docker_image >> - run: sudo docker exec linux_docker apt-get update; true - run: sudo docker exec --env DEBIAN_FRONTEND linux_docker apt-get install -y perl wget git cmake g++ make liblog4cpp5-dev libhiredis-dev libmongoc-dev libbpf-dev libgrpc++-dev libprotobuf-dev protobuf-compiler libcapnp-dev capnproto libssl-dev protobuf-compiler-grpc libncurses5-dev libpcap-dev pkg-config libboost-atomic-dev libboost-chrono-dev libboost-date-time-dev libboost-program-options-dev libboost-regex-dev libboost-system-dev libboost-thread-dev libabsl-dev - run: sudo docker exec linux_docker git clone https://github.com/pavel-odintsov/fastnetmon.git - run: sudo docker exec linux_docker mkdir fastnetmon/src/build - run: sudo docker exec linux_docker cmake -S fastnetmon/src -B fastnetmon/src/build -DENABLE_AF_XDP_SUPPORT=FALSE -DLINK_WITH_ABSL=TRUE - run: sudo docker exec linux_docker make -C fastnetmon/src/build -j build_debian: parameters: docker_image: type: string distro_version: type: string distro_name: type: string s3cmd_install_command: type: string default: "apt-get install -y s3cmd" resource_class: type: string default: large debian_package_architecture: type: string default: "amd64" machine: image: ubuntu-2204:current environment: DEBIAN_FRONTEND: noninteractive resource_class: << parameters.resource_class >> steps: - run: sudo docker run --name linux_docker -d -t << parameters.docker_image >> - run: sudo docker exec linux_docker apt-get update; true - run: sudo docker exec linux_docker apt-get install -y perl wget git - run: sudo docker exec --env DEBIAN_FRONTEND linux_docker bash -c "<< parameters.s3cmd_install_command >>" - run: sudo docker exec linux_docker git clone https://github.com/pavel-odintsov/fastnetmon.git - run: name: install_gcc no_output_timeout: 120m command: sudo -E docker exec --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY linux_docker perl fastnetmon/src/scripts/install_fastnetmon_dependencies.pl gcc_12_1_0 - run: name: install_dependencies no_output_timeout: 180m command: sudo -E docker exec --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY linux_docker perl fastnetmon/src/scripts/install_fastnetmon_dependencies.pl - run: sudo docker exec linux_docker perl fastnetmon/src/scripts/fastnetmon_build.pl - run: sudo docker exec linux_docker perl fastnetmon/src/scripts/build_library_bundle.pl /opt/fastnetmon_libraries_bundle.tar.gz - run: sudo docker exec linux_docker fastnetmon/src/scripts/build_any_package.pl deb /opt/fastnetmon_libraries_bundle.tar.gz << pipeline.parameters.fastnetmon_build_version >> << parameters.distro_name >> << parameters.distro_version >> - run: sudo -E docker exec --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY linux_docker s3cmd --disable-multipart --host=storage.googleapis.com --host-bucket="%(bucket).storage.googleapis.com" put /tmp/fastnetmon_<< pipeline.parameters.fastnetmon_build_version >>_<< parameters.debian_package_architecture >>.deb s3://fastnetmon_community_packages/<< pipeline.parameters.fastnetmon_build_version >>/<< parameters.distro_name >>/<< parameters.distro_version >>/fastnetmon_<< pipeline.parameters.fastnetmon_build_version >>_<< parameters.debian_package_architecture >>.deb - run: sudo docker exec linux_docker cp fastnetmon/src/fastnetmon.conf /etc/fastnetmon.conf - run: sudo docker exec linux_docker ldd /opt/fastnetmon-community/app/bin/fastnetmon - run: sudo docker exec linux_docker ldd /opt/fastnetmon-community/app/bin/fastnetmon_client - run: sudo docker exec linux_docker ldd /opt/fastnetmon-community/app/bin/fastnetmon_api_client - run: sudo docker exec linux_docker /opt/fastnetmon-community/app/bin/fastnetmon_api_client --help - run: sudo docker exec linux_docker /opt/fastnetmon-community/app/bin/fastnetmon_client --help - run: sudo docker exec linux_docker /opt/fastnetmon-community/app/bin/fastnetmon --configuration_check build_centos: parameters: docker_image: type: string centos_version: type: string resource_class: type: string default: large centos_package_architecture: type: string default: "x86_64" machine: image: ubuntu-2204:current resource_class: << parameters.resource_class >> steps: - run: sudo docker run --name linux_docker -d -t << parameters.docker_image >> - run: sudo docker exec linux_docker yum install -y perl wget python3-pip perl-Archive-Tar git - run: sudo docker exec linux_docker git clone https://github.com/pavel-odintsov/fastnetmon.git - run: sudo docker exec linux_docker pip3 install s3cmd - run: name: install_gcc no_output_timeout: 120m command: sudo -E docker exec --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY linux_docker perl fastnetmon/src/scripts/install_fastnetmon_dependencies.pl gcc_12_1_0 - run: name: install_dependencies no_output_timeout: 180m command: sudo -E docker exec --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY linux_docker perl fastnetmon/src/scripts/install_fastnetmon_dependencies.pl - run: sudo docker exec linux_docker perl fastnetmon/src/scripts/fastnetmon_build.pl - run: sudo docker exec linux_docker perl fastnetmon/src/scripts/build_library_bundle.pl /opt/fastnetmon_libraries_bundle.tar.gz - run: sudo docker exec linux_docker fastnetmon/src/scripts/build_any_package.pl rpm /opt/fastnetmon_libraries_bundle.tar.gz << pipeline.parameters.fastnetmon_build_version >> centos << parameters.centos_version >> - run: sudo -E docker exec --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY linux_docker s3cmd --disable-multipart --host=storage.googleapis.com --host-bucket="%(bucket).storage.googleapis.com" put /tmp/result_data/fastnetmon-<< pipeline.parameters.fastnetmon_build_version >>-1.el<< parameters.centos_version >>.<< parameters.centos_package_architecture >>.rpm s3://fastnetmon_community_packages/<< pipeline.parameters.fastnetmon_build_version >>/centos/<< parameters.centos_version >>/fastnetmon-<< pipeline.parameters.fastnetmon_build_version >>-1.el<< parameters.centos_version >>.<< parameters.centos_package_architecture >>.rpm - run: sudo docker exec linux_docker cp fastnetmon/src/fastnetmon.conf /etc/fastnetmon.conf - run: sudo docker exec linux_docker ldd /opt/fastnetmon-community/app/bin/fastnetmon - run: sudo docker exec linux_docker ldd /opt/fastnetmon-community/app/bin/fastnetmon_client - run: sudo docker exec linux_docker ldd /opt/fastnetmon-community/app/bin/fastnetmon_api_client - run: sudo docker exec linux_docker /opt/fastnetmon-community/app/bin/fastnetmon_api_client --help - run: sudo docker exec linux_docker /opt/fastnetmon-community/app/bin/fastnetmon_client --help - run: sudo docker exec linux_docker /opt/fastnetmon-community/app/bin/fastnetmon --configuration_check build_ubuntu_developer_docker_image: parameters: docker_image: type: string default: "ubuntu:24.04" resource_class: type: string default: large pretty_tag_name: type: string default: "24-04" machine: image: ubuntu-2204:current environment: DEBIAN_FRONTEND: noninteractive resource_class: << parameters.resource_class >> steps: - run: sudo docker run --name linux_docker --label "org.opencontainers.image.source=https://github.com/pavel-odintsov/fastnetmon" -d -t << parameters.docker_image >> - run: sudo docker exec linux_docker apt-get update; true - run: sudo docker exec linux_docker apt-get install -y perl wget git s3cmd vim nano libncurses-dev - run: sudo docker exec linux_docker git clone https://github.com/pavel-odintsov/fastnetmon.git - run: name: install_gcc no_output_timeout: 120m command: sudo -E docker exec --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY linux_docker perl fastnetmon/src/scripts/install_fastnetmon_dependencies.pl gcc_12_1_0 - run: name: install_dependencies no_output_timeout: 180m command: sudo -E docker exec --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY linux_docker perl fastnetmon/src/scripts/install_fastnetmon_dependencies.pl - run: sudo docker images - run: sudo docker ps - run: sudo docker ps | tail -1 | awk '{print $1}' - run: sudo docker commit `sudo docker ps | tail -1 | awk '{print $1}'` ghcr.io/pavel-odintsov/fastnetmon-community-developer-<< parameters.pretty_tag_name >>:latest - run: sudo docker images - run: echo $CR_PAT | sudo docker login ghcr.io -u pavel-odintsov --password-stdin # Please be sure that you do not use flag -E for sudo command below as it breaks all things and leads to error: # unauthorized: unauthenticated: User cannot be authenticated with the token provided. # I have no explanation but that's only way how it works correctly - run: sudo docker push ghcr.io/pavel-odintsov/fastnetmon-community-developer-<< parameters.pretty_tag_name >>:latest workflows: version: 2 all_distros: jobs: - build_centos: docker_image: almalinux:8 centos_version: "8" name: "centos8" - build_centos: docker_image: almalinux:8 centos_version: "8" name: "centos8_arm" resource_class: "arm.large" centos_package_architecture: "aarch64" - build_centos: docker_image: almalinux:9 centos_version: "9" name: "centos9" - build_centos: docker_image: almalinux:9 centos_version: "9" name: "centos9_arm" resource_class: "arm.large" centos_package_architecture: "aarch64" - build_debian: docker_image: "ubuntu:jammy" distro_version: "22.04" name: "ubuntu2204" distro_name: "ubuntu" - build_debian: docker_image: "ubuntu:noble" distro_version: "24.04" name: "ubuntu2404" distro_name: "ubuntu" - build_debian: docker_image: "ubuntu:jammy" distro_version: "22.04" name: "ubuntu2204_arm" distro_name: "ubuntu" resource_class: "arm.large" debian_package_architecture: "arm64" - build_debian: docker_image: "ubuntu:noble" distro_version: "24.04" name: "ubuntu2404_arm" distro_name: "ubuntu" resource_class: "arm.large" debian_package_architecture: "arm64" - build_debian: docker_image: "ubuntu:focal" distro_version: "20.04" name: "ubuntu2004" distro_name: "ubuntu" - build_debian: docker_image: "ubuntu:focal" distro_version: "20.04" name: "ubuntu2004_arm" distro_name: "ubuntu" resource_class: "arm.large" debian_package_architecture: "arm64" - build_debian: docker_image: "debian:bullseye" distro_version: "11" name: "debian11" distro_name: "debian" - build_debian: docker_image: "debian:bullseye" distro_version: "11" name: "debian11_arm" distro_name: "debian" resource_class: "arm.large" debian_package_architecture: "arm64" - build_debian: docker_image: "debian:bookworm" distro_version: "12" name: "debian12" distro_name: "debian" - build_debian: docker_image: "debian:bookworm" distro_version: "12" name: "debian12_arm" distro_name: "debian" resource_class: "arm.large" debian_package_architecture: "arm64" - build_docker: name: "Build Docker images" - build_debian_upstream_package: name: "Debian Sid Upstream Build" debian_codename: "sid" docker_image: "debian:bookworm" # To offer great developer experience we ensure that FastNetMon can be built on latest Ubuntu LTS - build_debian_system_dependencies: docker_image: "ubuntu:jammy" name: "ubuntu2204_system_dependencies" - build_debian_system_dependencies: docker_image: "ubuntu:24.04" name: "ubuntu2404_system_dependencies" - build_ubuntu_developer_docker_image: docker_image: "ubuntu:24.04" name: "Ubuntu 24.04 developer images" # It's broken due to some changes in Sid #- build_debian_system_dependencies: # docker_image: "debian:sid" # name: "debian_sid_system_dependencies" # All these platforms below are broken due to different reasons and need to be fixed in future #- build_fedora_upstream: # name: "Fedora 36 Upstream RPM" # docker_image: fedora:36 #- build_fedora_upstream: # name: "Fedora 37 Upstream RPM" # docker_image: fedora:37 #- build_fedora_upstream: # name: "Fedora 38 Upstream RPM" # docker_image: fedora:38 #- build_epel9_upstream: # name: "EPEL 9 RPM" #- build_macos: # name: "Build on MacOS" #- build_windows: # name: "Build on Windows Server 2022" # windows_name: "win/server-2022" #- build_windows: # name: "Build on Windows Server 2019" # windows_name: "win/server-2019" fastnetmon-1.2.8/.github/000077500000000000000000000000001472727706000152545ustar00rootroot00000000000000fastnetmon-1.2.8/.github/CONTRIBUTING.md000066400000000000000000000002321472727706000175020ustar00rootroot00000000000000Hello! Please review our [developer guides](https://github.com/pavel-odintsov/fastnetmon/blob/master/docs/DEVELOPER_GUIDES.md) before commiting changes. fastnetmon-1.2.8/.github/FUNDING.yml000066400000000000000000000001311472727706000170640ustar00rootroot00000000000000# These are supported funding model platforms custom: ['https://fastnetmon.com/price/'] fastnetmon-1.2.8/.github/ISSUE_TEMPLATE.md000066400000000000000000000016311472727706000177620ustar00rootroot00000000000000# If you want to solve your issue please read following information below First of all, please check following steps: * Do you have latest FastNetMon version? If not, please upgrade to latest [stable version](https://github.com/pavel-odintsov/fastnetmon/releases) * Do we have similar tickets already? Please check [bug tracker](https://github.com/pavel-odintsov/fastnetmon/issues) and [Mailing list](https://groups.google.com/forum/#!forum/fastnetmon) about similar issues. If it does not help, please fill information below: * Your operating system name and version? * Please attach your /etc/fastnetmon.conf configuration file * What capture engine are you using: Netflow, sFlow, miror? * If you are using Netflow or sFlow, please specify version, vendor name, model name and firmware of agent device. * Please attach /var/log/fastnetmon.log Then please describe your issue as detailed as possible! Thanks you :) fastnetmon-1.2.8/.github/workflows/000077500000000000000000000000001472727706000173115ustar00rootroot00000000000000fastnetmon-1.2.8/.github/workflows/docker-builds.yml000066400000000000000000000012161472727706000225630ustar00rootroot00000000000000name: Run docker builds on: workflow_dispatch: pull_request: push: branches: - master jobs: build_in_docker: strategy: matrix: # gcc-13+,clang-16+ will fail due broken capnp supplied with debian bookworm os: [debian, debian-gcc-12, debian-clang-15, ubuntu-24.04, ubuntu-24.10] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: chdir run: cd $GITHUB_WORKSPACE - name: Build Docker image run: docker build -t debian -f tests/Dockerfile.${{ matrix.os }} . fastnetmon-1.2.8/.gitignore000066400000000000000000000011001472727706000156740ustar00rootroot00000000000000*.pyc __pycache__ *.DS_Store src/build/ src/gobgp_client/attribute.pb.cc src/gobgp_client/attribute.pb.h src/gobgp_client/gobgp.grpc.pb.cc src/gobgp_client/gobgp.grpc.pb.h src/gobgp_client/gobgp.pb.cc src/gobgp_client/gobgp.pb.h src/fastnetmon_internal_api.grpc.pb.cc src/fastnetmon_internal_api.grpc.pb.h src/fastnetmon_internal_api.pb.cc src/fastnetmon_internal_api.pb.h src/simple_packet_capnp/simple_packet.capnp.c++ src/simple_packet_capnp/simple_packet.capnp.h src/traffic_output_formats/protobuf/traffic_data.pb.cc src/traffic_output_formats/protobuf/traffic_data.pb.h fastnetmon-1.2.8/.gitmodules000066400000000000000000000001761472727706000160750ustar00rootroot00000000000000[submodule "src/juniper_plugin/netconf"] path = src/juniper_plugin/netconf url = https://github.com/Juniper/netconf-php.git fastnetmon-1.2.8/LICENSE000066400000000000000000000431721472727706000147300ustar00rootroot00000000000000GNU 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 DDoS sensor software Copyright 2017 FastNetMon LTD 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.2.8/README.md000066400000000000000000000130441472727706000151750ustar00rootroot00000000000000![logo](https://fastnetmon.com/wp-content/uploads/2018/01/cropped-new_logo_3var-e1515443553507-1-300x146.png) Community Edition =========== FastNetMon - A high-performance DDoS detector/sensor built on top of multiple packet capture engines: NetFlow, IPFIX, sFlow, AF_PACKET (port mirror). What do we do? -------------- We detect hosts in the deployed network sending or receiving large volumes of traffic, packets/bytes/flows per second and perform a configurable action to handle that event. These configurable actions include notifying you, calling script or making BGP announcements. Project ------- 🌏️ [Official site](https://fastnetmon.com) ⭐️ [FastNetMon Advanced, Commercial Edition](https://fastnetmon.com/product-overview/) 🌟️ [FastNetMon Advanced, free one-month trial](https://fastnetmon.com/trial/) 📜️ [FastNetMon Advanced and Community difference table](https://fastnetmon.com/compare-community-and-advanced/) 📘️ [Detailed reference](https://fastnetmon.com/wp-content/uploads/2023/07/fastnetmon_community_book_20_jul_2023.pdf) Legal -------------- 📖 [FastNetMon Community Edition Terms and Conditions](https://fastnetmon.com/fastnetmon-community-edition-terms-and-conditions/) 🔏️ [FastNetMon Community Edition Privacy Notice](https://fastnetmon.com/fastnetmon-community-edition-privacy-notice/) FastNetMon is a product of FastNetMon LTD, UK. FastNetMon ® is a registered trademark in the UK and EU. By installing or using this software, you confirm that you have read and agree to the FastNetMon Community Edition T&Cs and Privacy Notice, which will apply to your installation and use of the software ### Installation - [Linux install instructions](https://fastnetmon.com/install/) - [macOS install instructions](https://formulae.brew.sh/formula/fastnetmon) - [FreeBSD port](https://www.freshports.org/net-mgmt/fastnetmon/) - [VyOS bundled support](https://fastnetmon.com/fastnetmon-community-on-vyos-rolling-1-3/) Supported packet capture engines -------------------------------- - NetFlow v5, v9, v9 Lite - IPFIX - ![sFlow](http://sflow.org/images/sflowlogo.gif) v5 - PCAP - AF_PACKET (recommended) - AF_XDP (XDP based capture) - Netmap (deprecated, still supported only for FreeBSD) - PF_RING / PF_RING ZC (deprecated, available only for CentOS 6 in 1.2.0) You can check out the [comparison table](https://fastnetmon.com/docs/capture_backends/) for all available packet capture engines. Features -------- - Detects DoS/DDoS in as little as 1-2 seconds - Scales up to terabits on single server (sFlow, Netflow, IPFIX) or to 40G + in mirror mode - Trigger block/notify script if an IP exceeds defined thresholds for packets/bytes/flows per second - Thresholds can be configured per-subnet basis with the hostgroups feature - [Email notifications](https://fastnetmon.com/docs/attack_report_example/) about detected attack - Complete IPv6 support - Prometheus support: system metrics and total traffic counters - Flow and packet export to Kafka in JSON and Protobuf format - Announce blocked IPs via BGP to routers with [ExaBGP](https://fastnetmon.com/docs/exabgp_integration/) or [GoBGP](https://fastnetmon.com/docs/gobgp-integration/) (recommended) - Full integration with [Clickhouse](https://github.com/pavel-odintsov/fastnetmon/blob/7f0ad9c6cd2db3856607aeed04b5e8125fad3124/src/fastnetmon.conf#L287) [InfluxDB](https://fastnetmon.com/docs/influxdb_integration/) and [Graphite](https://fastnetmon.com/docs/graphite_integration/) - [API](https://fastnetmon.com/docs/fastnetmon-community-api/) - [Redis](https://fastnetmon.com/docs/redis/) integration - MongoDB protocol support compatible with native [MongoDB](https://fastnetmon.com/docs/mongodb/) and [FerretDB](https://github.com/FerretDB/FerretDB) - VLAN untagging in mirror and sFlow modes - Capture attack fingerprints in PCAP format We track [multiple](https://fastnetmon.com/docs-fnm-advanced/fastnetmon-usage-analytics/) platform and environment-specific metrics to understand ways how our product is being used and prioritise development accordingly. Official support groups: ------- - [Mailing list](https://groups.google.com/g/fastnetmon) - [Slack](https://slack.fastnetmon.com) - IRC: #fastnetmon at irc.libera.chat:6697 (TLS) [web client](https://web.libera.chat/?channels=#fastnetmon) - Telegram: [fastnetmon](https://t.me/fastnetmon) - Discord: [fastnetmon](https://discord.fastnetmon.com) Follow us at social media: ------- - [Twitter](https://twitter.com/fastnetmon) - [LinkedIn](https://www.linkedin.com/company/fastnetmon/) - [Facebook](https://www.facebook.com/fastnetmon/) ### Router integration instructions - [Juniper MX Routers](https://fastnetmon.com/docs/junos_integration/) Complete integration with the following vendors -------------------------------- - [Juniper integration](src/juniper_plugin) - [A10 Networks Thunder TPS Appliance integration](src/a10_plugin) - [MikroTik RouterOS](src/mikrotik_plugin) Screenshots ------------ Command line interface ![Main screen image](docs/images/fastnetmon_screen.png) ------------ Standard Grafana dashboard ![Grafana total traffic](docs/images/grafana_total.png) Example deployment scheme -------------- ![Network diagramm](docs/images/deploy.png) CI build status -------------- [![CircleCI](https://circleci.com/gh/pavel-odintsov/fastnetmon/tree/master.svg?style=svg)](https://circleci.com/gh/pavel-odintsov/fastnetmon/tree/master) Upstream versions in different distributions -------------- [![FastNetMon upstream distro packaging status](https://repology.org/badge/vertical-allrepos/fastnetmon.svg)](https://repology.org/project/fastnetmon/versions) fastnetmon-1.2.8/SECURITY.md000066400000000000000000000010011472727706000154750ustar00rootroot00000000000000# Security Policy ## Supported Versions We support only latest current version of FastNetMon. We do not issue security fixes for older versions. To address any security issues you need to update to latest supported version. Debian, Ubuntu, Fedora, FreeBSD packages in their official repositories have their own security policies. ## Reporting a Vulnerability To report vulnerability please use this interface https://support.fastnetmon.com/hc/en-gb/requests/new We guarantee initial feedback in 48 hours. fastnetmon-1.2.8/THANKS.md000066400000000000000000000041371472727706000152330ustar00rootroot00000000000000Thanks file. For all people who helped this project: - Patrick Matthäi for maintaining our Debian packages from 1.1.2 to current - Benjamin Drung for maintaining our Debian packages from 1.1.3 to 1.1.5 - Vitaly Zaitsev for mentorship with Fedora packages - [Rui Chen](https://github.com/chenrui333) for maintaining our macOS formula in HomeBrew - Luke Gorrie for SnabbSwitch and help with lightning speed packet processing - Vicente De Luca for redis_prefix and InfluxDB optimization - Ronan Daly for Slack integration script - Andrei Ziltsov / FastVPS Eesti OU for testing and patience. And for syncing GoBGP's gRPC integration with upstream. - Luca Deri for PF_RING toolkit - Max Dobladez for Mikritik API support in notify script handler. - Eric Chou and Rich Groves for A10 Networks Thunder TPS Appliance integration plugin - 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 motivating to add Netflow support - waszi for testing DNA/ZC mode - Martin Stoyanov for guides for Slackware - Andreas Begemann for debugging issue https://github.com/pavel-odintsov/fastnetmon/issues/90 - Anatoliy Poloz for VMs with FreebSD 9, 10, 11 - Cojacfar / https://github.com/Cojacfar help with documentation translation - 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 - Dmitry Kaminsky for help with configuration sanity checks and fixing redis bug For all companies who helped this project: - [GitHub](https://github.com) for their amazing platform - [CircleCI](https://circleci.com/) for great CI which is free for OSS projects fastnetmon-1.2.8/docs/000077500000000000000000000000001472727706000146445ustar00rootroot00000000000000fastnetmon-1.2.8/docs/images/000077500000000000000000000000001472727706000161115ustar00rootroot00000000000000fastnetmon-1.2.8/docs/images/deploy.png000066400000000000000000006026131472727706000201230ustar00rootroot00000000000000PNG  IHDR.7C )iCCPICC ProfileHWXS[zW5t@B !!EւVtUDŵ,?,(bʛ$~{9sΝQ5rfRr h[HD:@d+2_?WQ$hӸN]9"q>^7/YM1$ g( )p&.&Tl8/f'Q)^!n؇gs! 񘜜\U-!LO?|d3F"( $l[r1L`a1eu a*giQk@|U4,~GkT.;(b=ّCztA bX{4NϊSEܘ! $8vX2iVύ|kgs!?.Q($DB]IVlB~`䰍X# 9!1 4G2"pD>?.L1a˹iCɓ$E yaE" <8l[*%C56YO:갎L "CV.{#{ fOxB$<$\#tnM`@2]Ы {C7u- #0 ~U:Z"ۓQ(G**.#^d ^i# 1Dž-% v;`Úsc8ZO#){(j:C} 7#_XsE3ł ~><&KȱtwlWl-o=ak*3p:o:pگHG*.PpكSTJpﲄ9W@0@HSapt0,Šk@`A 8N \w\/@x!!4 b8"D 1H2d BDF"H9RlAjߑq҉B =kTTGѱ;ꏆq4C E2Aw qz B_1fbX ``X k_ օbq"NǙ-ax- -gZZGÜbd3333>?7j騽.z=ZO]]}MS'X'KgN=]\Zwtݍt{Gk]2zz^,z CEO0 2 V51 W3|b3̓>#=0#vc x"z{&wt&m&}Lg֙6#֚1{onahؼˢТ%2ϲ*jU5jbͷdڸl6t!#S3-߶.®ȮXӱ)cW=3}6;^;Z;r:ќB959rq9otBwإ勫uk[zeg=zz{+k׳qx㶍{mIk}g?qs[Р`!!!u!}.B[aa+nYV-o9OScëFXG#'OX5nY01 DVE݋΋c"qbObbfǜN.. nyܝxxi|[jڄA]Ic$]HM$7RRO fRdœO2cʹS:=@*!51uWgvݟJ[ qWs{x޼rtg2z ~ PP%x)}VT֎ԜB adANXԕ癷&O..A$S$M}Qj)Efθ8zҙO C jm4{slM6dޢyC\@Y" 6/_4ѣ_B+V)Xx|`IR~-ᖜ//(\);ï.K_־uWY^^XhՄU KV]3m͹ Mk)kk*#*֙[sZu@uzK׿pyƽ7nY- 55[[ >ٖon^Ꭾ1;Oֺ۵잼cOО{3K}}Om=hvp!afC_#)m^͇cGQK#ˏR.::xXxGmH:qēO=r3zm9yy\/4\txO?7\r9eǯ]9}uµk߼1FMgo]p{%U׻_/wvy؇wqx,y{ړOk9>k x>y ыZؗJju7;:m.:v~txS⧧?>W~5A[̖0t^ P&)frAI9 +orq`gALo<NN#mH$N _Txc!||/ _AhS e"nVseo!qH pHYs%%IR$iTXtXML:com.adobe.xmp 1418 814 DiDOT(xb@IDATxYy: ;HJHQ$xiԞ[/&|}@W;z&H= Ж%cKER @@ڗsNU矙( x8'Tf~_~wMLK$u" " " " " " " " " "J &xp H(~p{8 C@/" " #mZu8`җl`Rj"0w/j{apL}_1Y0ݽ #؋|&zd/l+ysa}k//mNJj H( 8eT/ڞA8gg ]L:qWl]~E;pKu.֫D@D@D@> ΁>_E췳ٗt@6<} f{/~%{_]k{( UgpU~=^r gů^.P7 śRcE@D@D@'p^,=mّ/]tⒽW߷oGN_FչꅿDz'*sW0\Wu>u򻬃 { r r&6S TȲ6}+m *a/P|\XAH(XJ*" " "p?ȅC}o[^y?~87lLocJcv|^a(" " k/}Şsqaڏk<`>hOJZzaR- b5_PLlmܶPj엄ׁǍ@A(> [yi͎pm\:bz&Z{:aֻ-(˺5ܟ{ ۷>:1b.Xk/:Fdp>ZwЁh #j[km" " " d*=b{c/_ľ= (;hЋ%Lzm ֝`hTG.مTwb_6 v 6 {ڲڔګ/}>"A;;=@ cs ]xUhTEX^bm EWmq!v] " p`Z=ȃZwg/k勉`};BH'lo _}ڇ ܷo|b?b#@ $" " "  f sDԡ=dRL^IΝ<尬bb,OYy;˶ŅǓ?=bRTЩB+PE@D@D@nzpR\?x|hr*gyѓGG`QJrv-{d <=ߩ ms0l;|\2t*mO&1Uܙ}-HC9g6/-BC MJ͖$Ne,bq~乼iLΝ=fރGϬWea7:qON Eq89ٕ+ ɹЉik8RXmEX~ܟ u7:P\9_Lם9\,bNI(.`Ңl|09GhOؑrV G20_k^AW#>of;@קɄ Pi7m*QT\τNm\ Bid{tA 'C׋x^3UWFc<{P Qď1]uJxr"}~,+yjC֖ho#]8u^~ܷ|[MC@BP[X)Ȯ2~>9Q0N =V0'g n/_0߯޵;Dwc-O&}hY@'`sA<97?=YZS璃Q;|:}fB.{doxȑ,,y|xy+ @:7[ g6b_lły8H(;RWr=]^g bgA U@A=P|dGl-k_v!Ԟ?=\?ae,\̷r!> ѷh/E5d| c2AzG2;6/E1Qwً_Z<@=(0[OgVi|"߫>c*7+ śSE@D@D@Vu !!YQv\-ԑu:mw"Ƿd<ؒYI4Z~E@D@D@@ar6sòx_{F4G=Ds Ƙw#1=< 7b#R!AnDa{ގp+_K^ڡr4ϓb a9Ȅ:1繊BT .d m*Ź ~,{аk(0Zh:Ej>vXrԐ75/kxWNdj67 śSE@D@D@2`ȓSN+Os8):(gx " " "1OeBqI:U [eIц( j;A j׶+p%/w-%?܍j^erqlr0l=ȯ4yX1CCapg 5/EheuB<봡>UV#'Ggp̑oikxb$E k]{qska/zg&' xj@$w>[;aE.D,NXֹ){|]갲b}y'I Bv֙:c޼;Qk4ED@D@D@@Y^=H=P&8{$F>vBa1b}܍߫dRymIܞ{[l[w*#n.S\$gOk'ǢȦp{㔛C1~AAk٨ŴA,[?j|t6 ś785dF1^_y:ܼ_k_;(eѝOϟŠ; ך:Mza*7V֞ }P\ 6nD(]b#g;l2ؙR+DڜIjB>%a$:VZP|`m\JΜ:9b|37 |Ipf1 |\PNj6oA('I&nZo\+:du(ꞺP+CywZ`E~iر# _GP {08H^to^,G& xs~j@F XlwBۮq:nI̬C_&N;Nu2> O8(K_lWn5VF/ 'wLϕk(o{މ-\D@D@D` e0ƒcA8X?3a{ΣN(ً .>R[;ٷsv_{_BqN$[HD@D@D'EAvm(Loܻb89^0WX\?3E9c8kN:{g:8̿U%~CYhm*" " "p $."eUx qO?ŽvA= {>](zE\ܪq_|8^Dw=sa!\φe:)7 x7we\lf}Jb,Ag=\wVQ=Joapֶ.|(ÙA C4PLx{vަ–f1\Cx ^ *[JgM}xD{kLԃd ط!DOQ\Մ'2)wvYk\w/d4Ŀ߹JbSfcf#7&'K6?oTG99e1S[ S,ljƬ ḩl}O=A4$" " " " " " " " ""b\jSf?UG݃}HAujcV)C1q)RDn B!?a֓@ b xjK-ûy2P[44xxrOxؚ"m=m;\uZp|m-ւcm&jsfKlbnkK:kkOp]\LAD/#D1)#v* pc$zOC|yBt(K Ocg?cm\hE@D@D@D@D@D@D@D@D@6)$/Yux~6Vz%O EQn0/ "q=LEb0|DZ2c.$xpMjaCĦE@D@D@D@D@D@D@D@D@>$!nνEL-aHb(l^1Gm e `xqݼZ񲕚[ߚ.J,RCb:* d/^7yn*)7.~\\BUzE\xIuGf!@_#ٍ!< !-=|70 42CT Bs450<͚2 fbq#)CapTgBtsS?遌 kv2v<{]\G9ؘ=XC)~7:ey7@yfM3epƶ -fئID@D@D@D@D@D@D@D@D`#gas/l?I%^Q VJ&) ӻi4vuZOZsGE~"o"q c/-Ɓi| r*vcbҪ0Q4^[.j'nAT(B]gȾ`ueh@;: nLxf+X 0=6e;;\ f+W~ss"dM vx0_@ -/P;ͶY]J-" " " " " " " " "3q &&ll?-#NA*aP uK.nRm Zϧ_8AXvk^.#߰- %zBye.C@;p3kNb2bj dIj,AO^BH ԇ AAy ^3!6cpl-Yox]HMy앿K'C;6 x a(Q!mD8A٧q=(E@D@D@D@D@D@D@D@D@";kyM{njH2twe`K!!NÛx#9x:: 3Nq?X%<CO01k,籟",˸11j|Qg' @{]w2G6:j|!E /ld&Ax QNb7~?pwRҊ$P}qUl ?zW&Ӻ|`-3oU9sڶmBP,4N\`p Okۅ`]9p MXnAb*MrzD]}ˈkMsG1}lkX^@؊2CF A8 esv 7,ry/aۖ 8~ϨcNXFy<aȌIcct4ٶ>6`w2j53;{BFsoVJ]X!# Ca wXݝ4MiE@D@DcEJjo+#vM(Sӳ~1͞~1۳akkk{ FD@D@D@D@Dmx ŋvoؽ۪W _^ [l׿m^[Dt~Z8J8\(oK; gX[wExy\e`r щ`[ALA6J_V fnnj*\ЭBJB1S NvAPL7Y"u vC]>GCw>86׉[xٞE"O̽oj'hٽf@ޮ$" " k{'ml|nB~}Fݰ&Ao;{'{zȶn<$" " " " " m ׯ~iC?]<3[boP%ɚz멧a'j$'1ڍ)etx( s`V냀فAݸaL5+dnnq&F;Z!c=LGmE!y汏5n± OA5}E 2kULO`8G1ӣ2O3lH;Om76MSz3v/b,P6#/2q"B2&νRP`Bq5ey {{m+frعWqS|B bo8^vXŏwp b$" " "Ѓxd]xm{699 aP9ѾCVc_gbCRx&}XOcxskrr8H[vU;ʢBz5Lඅ`;Q|M_8oSQ qн<)8П~æ N"LCN?] ov9lk вU.bZ }ޮ0q𶰏q禧qat7g ϋN q)`r`S M.B`f1b|=Gxm;x}Y7q8uL ][cc#Sz{nn"T7d 6}Z 1ID@D@D@6+jqpy E_*!'4 ;ܿ'D#@jkmvјFq&ח`s[iLWd=]orSE`#nK(عs6Z Ky-.:rvurŁMP~kL A(nYٺs;JL0홇 _!kF(ef /'H ESLKS%L Q{Χ s(bnE@D@DE6 |3E^vf`۰q]2ӍuG|ظ-ģ7u{:: o-)w`ZIFPn;q޾mNϘ1et4" " " j`atڣ߼w1=7g"kr8ٞ9x s:s! :[H6em3ؽ|CGE^XN,Teh'=ɶB2=ixo[.FjMS< Ѿ^K0c۱;t٩N;$| mkH'CRZ~_f.OR(;Ķ#C=YT>3(eܞzk;a+8wi=ݓa3?XS3^l,'v᧯{gahjm6u[ jblfR9>Q(nNć[ أ{w`X" " " "Q=tst܆369~߸հaPm A\Bl6Ҏeحc _9 ~ 61>{7̷uYgL[+F-*3 Q wA1d?cO=VWT|=.t7Y B.ikCLt$4t:0Vۣل&&qqGy󅴌aWb=(J9Vs\s#ׯ{7l Sx񇭌/=!lrG]ݼvOwY{g/lA}!>:9{A<xlg1CQlXޮ(D@D@D@>j @h"0b`HSxvBB[ÊNj|NNk4JJцphovSrQ8nK=v-z{Y> z)7N)UD-3gԄ8uFQߋ[j{ #t'; \|r{Ǣ/<F3䃷aW!Oy60L `] iw<_\Gɔ9bAB!$^~4 o#,-cOcwCrnAg xՍd^{p]Q0.8X6 y OAfx\'rw;C7_N@wFEޞ(َG ? xs($" " geI r7ȵI{7/9睘B!vRx(0?q+m3emE/ sp1F1_]fa+1Fw-}ȞOx~E@D@D@D`=3=) NA'&&m|l&AzA,YN~a'oliL6݉"0m 0+v8b/=Y. &A`E&*Bl) faÉ}+ 5ljm rB(}4Iur-"Npr=IşfBp{b1ʧa(L8kG ZV^>a@y8s`Hy`].铣Bm;mǎǫ"Hbǖћ=xpq͞u x("sH,: ڂ[;[Koc1)ZJ͂ >XnCxnh]^0QnpYv<)6Ga^dy!EcZ}1s/OݽvSQVɮBqzt,Һ=0b?H3̞`M@IDATvE@D@[?&Ww߳n"hoH= c qaG"/mn^˝e>j፡G91O=Eae+CũZE/xLbxpbu,cm@(M" " " $! 1yGO^3pă4PHprA{o*s9NP6ҭs]ܥsE^Ix`C9Ebj o2386sae tNE~ţdwHS&nK(W.o 0{ ZbWmq|nt'YӮ=zB‹ ;Y0!Hy a)c%\C:TY<:U'17acaԛCGiR ̎Nw.Gۆ6G+0T'g:'"x7O[=E{'4|ˉ5thge{ñhOzGQGf=q&ͤID@D@DC6CLk K70ߦZuUŐUx3a&|)03!ҡWXmC)!yajKۣo&"C ^}=bhI|\bq&"srrOQ qJ aKy,l{L-clGv/c֥I6+Qs۶hk][X<'x*WyQ q6Tx1!T"N=Wk 2ssӀ2]$F~e\TªK(?\4h^kd< KhOBE\jQmFto !# 5 Aw(p܂.P7 ?f0B% M&q< Uv Ua<6;n-/j"e~2(- ΋<ۊ8.Çu ,sFC8JŐl>⇐Lpx<8kN}՞|^isx_'7m ,\~ϖ_!cv[]6mfTv} ǵ DKtn8 Ll܄@`'q* LOVx7׍Y1ˣc)!Q$v{G)c?kF\2A]aPY80J1kl1ˉOxᓮE\p51WD!<Ib̧o表?D 6 a$v/#mP9Pg5iir,Bw }*#}X5@0砄<6ͧ\%{xGh' W5 ~Jv`w^߈G<Ϙ}'tCkID@D@ n#FIQl7{;%\2mT"ե`׌uHz Y1NDGm O3BcQ0f'x/yBހ va[iS(d3M'/#V?<fA;=}٧1oǘ_GGv\5  >30㝞CE[$O6Dj#`svL1]i`_IyCj31[1hq[ c~xޛg^(ol9L-|x+#nsݮkx^53{' L!;M5Gbok'~H{(ވ` oNGD(Em o+lH 6RM|%}eb9!wZ Z3~ |iGΘ&@n0<SA;8yb,*|(7!3.gv_+M1BbYa=n[# -b04COP$챈\'vlbS2&(ҫvsl#n_9 m;ta{v?ߣ2E~!B1OKn.NKbK\oYPQ@giwlWWy|k[ĉ0 Y>' ē <;]#"A?)Q>:c/kh s^g(3s 8I4ިpS$fCgғK2xyX">2_5[AC! 31CCtݠ/zj t٢ؾ'6Y!BŽ(5KeS$nE>P7vPr'11["/v~sIEffㄬ>1N@]ZG&H!c;3Os%Yv+SmF  3gQ8-:S7aθËM':"qx{>9QA[c plF&w!F/,P~țՇ{1!4Lz9wssQe=6%giXXn'̳uv<-n9a cl]?dO>>>xoZܯ``A2(k0_%\[>]x xsqFE;-i -DvM,sC>CD8C@y&2mBq0MPp:kCe<6x v4OY%s URCYOyJ-H oe=1Z;K0/ф@\oc 8RևvCoƱwx@&zgF[8 B:ʶco>|gKֆwM" " "p~:QQ$@04ѳ&uvH8[4Ң{̻X8ady^%toY=Q1=SlelXȈnapee a9_O{)y4ywu!e&O ?Q{hpݳ?voD@D@D@nYy=Ň Ӯ=)ޫy_u{{1M%BФ :iBinU-Y>!/=X_h P7MPpW1]9q ưIAA+ (b_ė+L޼5Ŗr?kRY˶o~/FT:U^D#'pKxй15kU,Sߥٿ uapvׇB t?ᑇ,RxU2Fq?yOqIi+O2n: K1Q9 s/f.a ڐV6^9|8`vl.Bet(ۑ_&MDa웂܅! CP,"OuBoC9\q< 5qO6zq`4E? r*q{g\yH8m6H7%}>Y/|d_}4'@w:8a軆i4"簝!%x+EW|Zv0;S:xN,JA5erAɰ:B^!%X9P/lClJ(} ezz0vq[ㅧ1N5ҧ r<-Pg \`)30߲{vN$" " "pk췿޶ᑛ(@qȟܚIӇmylӆٶX> i}a5)6A]z4,im|=)ݎ AVuC xGzIQ6j>ȴ,;,v[vuPeSA Z,X4(ݻ1ǂkI>: 5<͚“wocv\xb70/_~זEݻ䋣7 a *~T ]=?R+!TDzNJ=AE94|B5lOLt(~.(;B~h@'=2:rfz2O;ıGބ^ C`!H(`oꨋ<\a"(AG0,_[E/0Y+ ߉ ZPeyAAYP"/[<ǃA3wQ#XF䥈3cA,j8H7NO7~|!'6XhP ^]ܷl[o>l? O^$" " c+#md䆇0b~Ga7=׹HQĭTf# 71Hۀ-[njǐ# Ma{8 (`;F'ʢ }Apb8Ec-(9*; [uM֡B:8-_'dBZ_b؍kmx*^p0,n}_%& 1|M| vuojre}x y%g HZ\6mta{XENOW̛֗2MyČKh@ "=b0Su8a c\=SVLs,}8p \el ojt/czKkBq^cxݛ 'O_}xwAB' XFnA<᩟aׯXІ}5< kHOp~q ~ { /k)l'|2:%zaK =aowq Y Gaނ@vy*Ub}{5~3eq624%,.p9 _Ϝ}UQX5nL SܝCk=|]|3t`':91c@v!Q!2 A7?^VBc\T>mljSPv( 8xa |i1xdx )S4avn],?S{ VVv!J3M" " "  % ##ܦ9:iS3ϦC%X! [e,..2ö|{6G;Ӆ儼uiR>]1oZ:M

񊱯 qЕh04E̪xmgLjTe('l aUR8fBVf=}8=/8UueM`6EƓu)IAQ6IL-E۷o??O~wAwĞy۷o f}N ׯ_fކCKB*QFmia4yc%xOp*u?!RuIEO:rB6s˜…$.&\ K-!M\ B+Ӆb](Fǯ I,ella zJ|M6\: %tfBGo.u3ƀm-ȵrs75Ս씢8\?74< 6( ʣފm`@LJp~grc^]g](gGVd}f]jUx!)/Y{Dh+b_>dn KsXIvBbppq9>rnLUʕBq 6TxPBl_ 0潔9\a)03<ӛSk[?N;Azچ[vC4Oe޿ߏc0YB BM5^x 6㡝x ?_ƀAW-=s}Rjӓcvp\%N '/z^|E=A_w//| ?o.dID@D@tE8>4x Yy_vDB]q;S04i}ӄy=" ayo}|3Om% +VGјo`1^,ni8ӖᲗ93bcӸmp_nObXlvc!d1-֍c1~1j{EB1OjzNMt(wI;8]ɏ[$ |bf>fK3S.-iVsؓ~l~eKxE4=b1C'-aΗ894L < ld pBSEtwS࢔^| AO ';q7f[~"oê' |.RHws| b2xB(v^< T8Vm YvJfgBz%7++zD|,ɲbFݜlP & JČOA+{/ia0g/(z"7^b&&1G!.5L nA nQ (sߐJCSwvg'qe+qa3Cˏef!( ?V"Xo<> m9nt$ fN{`~?h}yZcD@D@DA%P\C?ѱI +O^G; z1 6@\L킼b'pݢM;X_ކy؎D닭+!Mcz3m؎E_1/i  }ю<|4|>:IbWhC/@cG0]+ò2&Gn) <K536>3|.gޛH]'}}}%HI$D%Ӳ%b ,/C0H 3#Ӝǒ(HKM6dUյ/YgdFFF68/s^dV{ND{FCYаΊSlE ~OX{>hkv?{x%,`(O߂OmW-k  t~, |%=R?~#P厞^ kݠOX<}\Z nOWd#ŏ^XV;ľ96\_T4uå{$a—V״`}U5NKFt&@qxpdž{Ĝ7|6oj'D7xRnBd.5;YGu;ubŞ83h' Z5PܼsxzzV;~C6C_ԄF@Ӗ("vu[ Ds0uyǦvFG^܏?ƚi*z hդㆾ SWp.pog!nȲY_ЬL@|Q4.vǎ!=~1LTqzCihwo90 = 9:pƸ49BOM1c@\ol=H\Z %@9ǘ)ca3*﷟ɟ'|ҽ5P<@\9>8OeI{qޔaOu6D䵶UlArl55zh=ɧFMG"²+bZS[dL7 mʫF]Ų󜞢lأ7K{mPS>t@tn{*5'P]Z\Ӯ+W+5y[Z c[eK:x; T 56Ѣ_ejMGnZ3yHtV Mi_߰u+P[V[\+ I5-U^z4:u)/cwcHwhj{_uʉ8D_FVGp86j?@1%H 75w7o;se$d$eM4/[Mi` ^ޜ?9`fgny/-U.PKh趰/p* pt{#=1'k k k kM8]x&'S#0Wz~~QTsVK1/cBpNT*yS&AaO4툭miȹes5m 1v5cO:rQㄳ4jC(d\)#Hɔ}8o_^l6YɆD e!kRo(h3.ykӆ'Nx}g?&uymuqU:?Q[" ,("kC ~HF>y:f]z(&TaL{s3lo`<(93]#B$kԦ!7>9F;~UJhKx+)ëxU7y&*-.+]ѨC&'*@ 3ЃdZBM 6'd3 ث <#K}]k/دɬזVۭ&Whe}cMZ>G/wGvB;u=zf|3>Q9<B9d d d d |haUOKWz(Sx,x7KK 7rBU w+!=\a>c%yqnvZ`<{-R̯x^cmkU&EDFml!ƳvE;-U󳮗SC qXb BMFeM+6?'gm0N(q ݓ}Q'@b[>ɓSOg#(!k k k k kAl{ykcN&jkaMss ЍD sykeBֶ46L^nFf$7myѨl2bDI'7%OaAas{[5?o9b:1C6tԣ(޷oXog #~rx5bJr2%#-δkbMXYMFUPpu X (} 0X 0*~E׺+CtmOséVuPU8&#K=svbo.g,e_` Xtx@7ayw8!k k k k @ԊGSzʅKv^@Դ?ŌGu]Z-/]>7hɓXzwnbڻnëZL~BZO <=Ym_&>ԎC@@@;Sx&&9bab7nk17SٻyH^Z M|j.enJ%%4bxe4J#>39.O^p1l΋ozP WoOބ k$ 4tHXbgM9<9yG /4tiL"oUt\vmC[?ll{/ռi/΋qk>},>u}c~'k k k k k_~n޼17CAXYМ6CRTWIvP#)ۣߔNQ[RLoʥEal+mGG yn,ŀS͚;_2Xߠ@17% |W1`2 ii OhD~s/~$;SU]0@1T} W`kLHN &_`vykWAHFZt K|m]ۺ8$cFx4'/n؁c}6_{mvKG`q%ܛx-}Add d d d d 5@[|ddNd-Nj6]V^AL!Kjk0F~ި-R!ߐk (wֺͶϿM; RE521.(w,quN9= 7kk`WkDSQk#S @=Z4i9ؑ5N#gXI$u]~i-! mWq(VO~ńl%Ph6w mљz\rcm狳捉RWf˟Fhܣ<9ӂ::TZbMl"G߀u=+Ӵ(+9US䠔@b*aiT۞ w@IDATJvX=+*^J0|{k>5:Ӊq%gQVua{>xԹVBj$ Bÿӥ1ؗ|b\w6ui8N9D@cU{י;oM-mp!ɻun5559@º6%lػ[yRmј7inM衲xlGq 1^N_I`aE;(ޚȩV뀨k|49l dQ3j.]қTm71!!G^\[w*'U&d#ޫ|6qb^*1>ǂswxjO <>عs6D"w3/\{pĜ@?Y,xZ-UhstSL7VO7Q)#Yh*EFl_3)繍 '8{ktyCۨEv}9v\YG;x #蚀bXh'aD{W 6%#RmnVj&6}a^bu ,޼fK/Ј'GgPHXA7Qb[޲xj <; OcˆN|yk$ϢM- d,Jy8sK,ł i,ȿ^խgB85&@2J3;e3wl\mC}nwN DFn;6+浦k#d(8`L;9d d d d d 4ꫯR0C^vޙblJ8=ݢ10Z H,<=ri'CUj/i[vokZFhEX\;lȨlnNm- SVj5y`c&DCqn=./eg0lH))vE84 nUh!3Ui8}o6~g½~3@qm]8θ5O1w.x^v͔6:wޢW2@ŰukEWqԍd*/i>Ze,)#h<%EG~qu83Ą4ʉN6s̩+!y̩^ިLS.lb;|o#dR~k4ӸZckޖ".BG14fwWlθp< Vƶv{`Ǜ`~>O>Ǩ'f 쮁iޞ-;onxZ Z<?"n4涨wmm.[mfW֗#0sME1(79q pr0 fu!eu/(6icأM\ ح*" 5V(4rb<;ǫ6o'᤼۵Xh<-p+%6ٓ>uR{WOESZln] ںn*x=ߙ^KJk+nH /qg7vbl?7kn.=٣l8k k k k࿗0VbqI,ebIs=/1Ws}Oe!ܪEm!qQR5\Ȍh *Jp})2^A6c xZM+^9q+hxmH3d+v״]Cu2m\1~+dk#1|Oا>) &wx<>>nW^E9>*s1VZyN|*Ksz_+Ӕ>2K!7o.22믙os[+jI9X{ԄfesV-lm<'JE٠-|Om1}~l:ujJ97k;s v]biWYۼd| 0y sc[^cB+* ے@硣GWŵq\:ou :>6wٰn(.ݼp#Hhx]H|=QA#ϘAEWIxk۴h'{ܤc%YZ#Eu`fجyݒݥKʟ>vU6+W n?ꂍD[fa!7{I/Zh& ] ӸW`r-b),\B7_v:miVM H\'q^[4?Vwڠ[P|A;~YYYYߛkˋu#XW:x8,<1tnܭ4e6Op'88)nwk;hAmT2Qiݎ[ ϝmmf| ޱ(!<׮]s%=sI<v6_Ҙwjr<qc^iKpu>'M)!i"v믙{[>F?5pD[ٝwo뭽i=N?g To=y¾>*Y@qyihQ66{ZMF<`1_2~- W(Te/鋾̍KڴK~tC sjQ-yD^ŝXv/}yUOos~9i,޸x ˾&oU+e+ay=$S ޟ^u]Rlؖ(ht)['867%``^xغG'yh,WOCA+A`s_`x{m@-k3#Zp/o Gxߪ*ie-{]ܣ<oa€,8b`s!p|=W^uYn W칗ljNmxy+x;Æsl=zhd:\Y 8]M yWmkMPl^SZـ8SXUĿ{UG%Jp@gDࡋ.^_3H%E_&lv=ƅԌߚk1M_ p >|l]kxnBg}ڼꉎ=kg%>o^#K:Lgh}{# &r555hKmHtSώcy[T9aM(yp/7e3M^y?dGՋDzRmo.d4sĊ-g8=[:yUgW&'{ Is/VfJWpɫTB5c-{*+6b6^o?h;;G? kxFM]Ht=ԟ7g 㬁+*Nv0̥̩ۡ$ E>R.oaS5liRq u8o%Zw -5*6*mZk|/kef&mV/,*2[&;nBE٢iCq5%]m(_bc VGhśx]<jgX8\ :qTLUPocsj\5I7Vz.8WL_w7'?xAChkR@\znӆtJHVwU 1U/W}6*/-Fuu3/k]]aiP0弪hn/җ@6xUmÞ>. [3*Y-jpxM \{{8OxCX̊J, I* ޲*[g^ΑeKEZ$\?aZY}Q88"&?BP7߰|3Q㬁5#P 5/˫`IkDޥ$pr]Ffܮ45nzBpOWC==4`Gi8%S}N?huy;;ԟV֦M +nYpT.4P=t.7nrbF-J>22P]g]Dq$5+5q/>йaVm_ۚ(>*a5# m639 ,W&y'cA 6ԓ,:z(O@~!xצ˓X6= 5;bO7 1yr18w,.BkO`p 0c͟ @qK:dG=u̎k:b4sirDΓZ`])O&ixqLNq8dP47n!KX^\ j)C.j#㺢vqS9 X%w||&%Qlyo^P4n c@bI_yKy&*4o-u'IaMqV.4|OyȤG.2j9Kݴ#3ɮ@/I{/y)$e8>l*KrM./겏j]N &Fc$^H<5y[N־M !KSGQ2>WBȶM}vzνku_(q(FM4U<;zab}kmM@$?"z,"/L1`1=y뚵ȕ c7_-k39y2Ϟy}FFmA&4[Gȭix¦jF~>@Cݬ6$a(v$  uCW:"& mw}C`7SY a\,@Y|.ACMױ{6c~qw@+Ӣ>)Db:vrǞ8`%d (K q%{,Nam8r^+N@1tbWyɥ/F=3~ۇYF &r555hcXh#cGA=kI0F:$vM6j6(z MQ0 #)cy3Q*PkW˳xA9ܸϯ'D?aGb0b۝2b$ƴ]'ݲ1I_вabXs6(6YYYYYok Li+W͛7: *ih((*K5ݵ(ζG ԰;Gj'ҤK'ok~S..2 |`\n@[AoQx0eSmJܴ8ؓ012ܝBȤ19N&iBR!KyPc@lddx+E{??m+B2]5+PWܢv0X;w 8`14uy (nb@e;J뜾c.Wax`rIZ߾QWlo:ݾ{鎒eyi}16og1ZGNʈTE!?5YQ Y(+:dnIvV3ႀݚzm)yBC1T~-LldG%{ce/ *N+Z^f#/R Ź] r[uS]lbFcr"#.j\`#'!m^(4N5hUȵQn}9}o^0f84㬁Flfw-;+ʉ}bE`;I6 08b/+n#hG`&HjaR:ɞ"â ,kW. جvц{Ov]aNZoȏ8P4r2}DXv*/,!axU f4:8*vB~xv*o'E;iMeg8 0 Ψ+N?GbqC(+lg`/򬁬w[ ̿!ʜ\oy&%URɬ{M-,Z GhѳILEO6^ Mh 5yHpkzZ3Qu/`hJJZ ;nm3X;9Bn*98:'J#+$˹!$ǚX=wA*ݩ+XzHm광f9t"jby 7C,1^}h"bU><7 <8 oI N:\/߽݂ y=3a $v m4;4 8;5@ޮU;o9='d3R`e!{I;,7?DrK\  Y<'d$=C>$ |}:fOAG~^^oڸN3#Z0+esC%+00mfқy!uO//{On(wxJė&mQ`pCr]exW鼿Y8 zLH~~=; U~x&j9vgh(SWOf&v#V;١+tiobƻ pK'fI28*tIbR1N>R>Q2 ˝'QIajo\rߊڪ(dVھ Cip3F6ʃxL7?zkIܓK]b_ƕ[m:^zۦ]xu;unBz -csӍL} M"f,D~F9s{8osY%7*{K3PM8I"g}߇78PD{<6X~BA9pyM[ʶ |<}ْNrI!l_RjCϦ5W.uy=wz okVk8>ȣHM&ʶk#*ƭ(ӟ~aQB)*[1>A%.g܀=m}v;vlx+Pf͆um?i%'!x߇}o}/AEybn9}U_ Ikk E$48/cJ|1dN[}aя vX?8xOV_+懂1`̝ΓnXԦ;6yټ^`;ڹn[zSzBιeԔI%{ji1O]zYx )s~ =O׹ 7!] ba Z x5y /}^T6hq8N@1ĝ>܂7qD7I?w(F\;r.2=8SOs555hfssQL `tߐ{ aP895mW߰5~#}΄E;ޘ\hNtQQO (L7l}߲+^a7! 1v/zl3aZ5v1XD:6@-tjlJzh,tb\>YQ{__=ӗlWvaSGfB9xk9gqOReZ@Aވb~y]p,lb^^ & $I[qWQm2)?s(yE^#"G'P ֳ"ipVhS?I.9 ѷ'v`jQ$tqHL\] ea[q*̽O`_S1խnBx{՗@N~\\b4^M8k k k k7nW;wܦHdek5eb '+vcGQ'ok~S..2A4*Mc|L#qq9„VG a&&blE;n#vlh#qN=>Zb![ /a9Mx3re(?\'> ߴ?ǎ3@aqrg}~n޼CO=}M ,k5#PLiEսGȽTZNDEЍDMԑѦUUjj@޳8pTgmCyD$HMuxh=I:r 2ANٌ[FY݀ƺ(5^g : ]i_3#gn+ؚ@12!=!qTru(ë`z(I9?k k ki 6;sm$ۗQ obCYsǏg;|h784øOsNhokiLEQ'd5o.-UlfzJ--FhkMA:d_LlkN2QbaQB&׋ m L68q+#/=fo_zѦrΫQ?S?S;6U !L"K.710t옟C(/ܫ.8QvTaD(||_S2)EhDVOyT!,g. . XqkTk0s`.nl'@bu]%E.BAb ᰃh0P/p8t{Bv0~~ftl̆uxs=vmuȑ#^@<33o1l҆5~~yJ!k`/ W+vք(zw^|s(R ZPn"aEaf7tD,ţJ(( 7ҏ~+M\ʂR[((F[<<%X<^[h!OlծCQTsc`vK1ۘףoC;1#xQe&'v;HWtObzg?o?}ׅ*{]e 7,j@՚n쮪*ܐ"g[&]g6 @ 7{k}61+NU}&!/nm>{:GzMst3gIMp>W/KWT)f;x@sMqgldxB  sR6xa (ȏĀ{l쥡\5555vP{2?p991ܭ ,o@Vtino"]Q2)b[)yҦ Ԑ70ͭ!>4`3uUm @J0m8'$lh',C]EE';("D?cBv x A ́uqkoV {~z{ ?.SNA}K/ŋq=zԿ]Q7q??1ϳ@1NPp-+f>~9g[͕XpAs>UjiL`sR[$;ͩ,5Zena6KDpm\/(jD~M@w,ktɧ_<~y|A&?7tn8q~ԧ>e<:Â{Ә}Q @mjjjhɏ~~~O.(.߽Fc]tm:SںuyBྪJlr8c_ʡ#m>!joO*C6Opw T͌`SNtN~`QpYJU,xMT?lR'lזt恵4?ȧv<11=F *ېu S?-]>= zD<$GTp ,;=V/XOI;p2V}15i:)Y };H(`'܃c>pn7+zay3O!kj@IDATϳ6I@I֭v[ +<\Iz[lnZԚךnO,DZD:ʋuVp 2,ȣ,ʙ[bQQ,DKv ~}֣l_~;yvX }CYD {;}ry@@@M<|{3?<Zr03b~ǬacZsS7s~34e|u8'$t% d\UO[uiAŋ~ar v#ĵDz(C] @7VyxaE}\^x~iϏkCoܿb{Ns^@h஀b~x3 qSE =TE<)O| kpgo`z4֏Mإ %W^VU>b&ޘcC7.Z8hb 6pu뇸'PZN{ĮVft`r !ߴy_\n,]VMK`.2`2KHTN+cuKrpӦHSCE5hG%Ŕ ܒgE&`<;uZX4TUɕU.Zݽo>Tk#n{"O$[ZOb6BlW^BSjxwIi-$K1(80Q_e(.3PWv@_ӻ\1 C땹CvQQ\Uц-eQ[Ճ6ћ#Vnx:qN[6dZwj'ڻ7" 8x%bȂ c#x(.Xmxُ_z?_ Hvb"}*,b!FlQzYYYYk`^@Ә6?"cd ./.Ac8lH+n?R1? !@`(8VVVk| dy' [[8S[mHxMsP&M; 61=waSۢ@ʀh |Awƛz i '??co۾y;2VQ3l__򟬁4pW@]=aצf#I~LQ$W}R~t}9A+0^Q~ucnl`@됼jەTپWgfܺw ]~㖵m6 bVvVF7.]ŊрmdB/k0 %nSh;VYޚ 13Bbu7[b;tz\.^Auಆr̡vу,O>_}o ?K_;'pn=[a1mvȘi\(0n.RYY׮6 YEynVwyQv|/19Zei żHGHL xrk'GbÔX _]b}8 OF±=dOwُ= 7i;\j=37a_xtwWu}pWjBYYYY`f .W+suxQ]^%I=ӴkLIv 8ob)/BvCgpkXkb9Q o׀\DNk}G l"\c['aʰIKLp 5 J<~7^8?KرcSO=0 1x4i1Q''{ia{ -,q}ڀu᱌2u;@1?^u^~g7oJܿY{ \燦M$N]'@y^+r.G-S m Ukk~]<0xQ Ip&_B1"~-ڷi<#tDsO` XK`a<9L-PItc yOyEuhO/tsaɁb\,-cv<+k\tݘJI{y.3>rx]:rצsl >i&зo+DTn>xuq -.y( d d d d 7ܼ=gftKVkp8k.ܯ9YGyqM # 쎸&JE·n\qx|8GFte-[*G( ?aWE$(@o,9XD(A^: !>9 ~W7'O􉍅 {=n;d#UҍR~C\6G hoŎWOЉRF7&3C#^J0&ܴrxi`Vw]>p(5 cch5Ř]zYݰzkncbpxN9yA&,&mD;qn,Rv*'7h+bڌ]p`BXpqV"Dbabwנ)[^ϪM5u$.G4~]OXolxo5C@@@@@iիW H`g>.$p8ѼnS ]vj>P$6v37m~vJoo_v"Dn]l9N&2ƀ6NaE}l7l<[ VSڂƍnS :0- k.?x1q>o}[/~qtpwwUoXq]_zoƂ'3oa9m Mz|xcG͈lYw-@4] &l@찍kإۓ ;w@PzGjiXy]T u+qY^AM9qX xdݡP[grGS6t[뉕~l7IZ&w'NWhugn>`$MN*^w: @b}9߳6FǣƢR&L8 3$jHtˇ(b*qӲtR=4h{Qۥ]eRvJʅX:U }v&Dn0iU$17ee n;ئ(JhZDbǪ; fRKIl$^"ˉDLNGMERIӅӥ{NX-ݴKOV49h&`&pF8??X?jykim8]P큙I4TFNsDCSifB4ΝqG1sm,S&.9,s0˕b[fas:XSI[Sw\Wo}h.:Gʋ&>'??u؎t O!L{[Imf{AUO[/ K(WxdXmoRc>)ӣ|C(j<弞7f3s\n}ӖcBXݻwo6˿"vm}DBDS0-y0h^WK/4%:ۧ`4wyga LGo$8?SIf:"ѿ82s=(MG1elƔ6 ,I(~u_80GcH+RwŲr !YXOT079o;$ԁCsY._g'X"*Rh!4"#~"b3ѬiF"e? JVRI^8,/Nrobzľ6K8nVYQ.ՀnHwǾrW;~ 2h :tGS#ۏtLfui0Q^F$ Sy9n~ԅ|HHf\6MzmYk_V#N}}^ cXсYb&`&`&p+>2b!Ŵ1D7S/Ƕ haHG>yQBz5+G 3g=1sḜ}D{u ˴7yEpMD"l [XuO|"0pr1{M[$g`%ڗH%9{?ڦXI 5LU<6O(ˈD7c+A3U9[I Ev{iq"F;89a^aԍHd&^D"7iB(V@ Ƣ?n(t5I,1 |bZ8o`,۳5FS\GMr[%*KE&5O(y bBnVD j5E<#(b]T$&Fꕨ&?f XNPOɊ:iN=^:Xq^H1"tWt㜒ʀDb}IpGN4Ǿ $W'AP/M5e(u +X@'-aMU񶕊ֿnu&e4rk/>iqF&`&`B=bEo޹3e1;u`95:KCZ$w!"R4*%n(ILҸϡB8|.eo\g{:o;:#bk1Ҡ|D蠰9呷axO.k?֪O1u BdžȖKk]MLL7Z4ѧ-f6L$|0or9wHj4reh#ƜOY96YnlGsq~x҆ `o6E Qp)a+J{ ;~)Zeɟ/,%J?A*;"?_ԎçcG1LȃQ&3W]uUN Hd@|a?aEҒ+YiMbnk(a9+CcL?~g%IuD7cAm%EٶhnMa4%gbۡHIAM*?vxQw XD$Vk]Ee whC?Z1&k 5+B1M70>Qʊ7qf"Ng|UgH쾪5hB .<4ű*8k'0"5DIu=^#eET,*e ZפsRZ,쏥ze>UIX|Y^cc(㹀108}0nZEJ,v`5hUŲ\:49>o(xJE914 Lt6rq9o?ϝ5ȝi>GڗsD茐?:ty^`N%҅sDQ"~2Seyx_MjwPm('?ABp΃N %@;b#N 6 siC6ENy[q?3%6 R>94϶4^ʖ`3G s K16Hp>MQDmu%achvU|{'?Ij[e3~ėd[o1B1 *!LyL00Qޔm\VC:_,*(o" o8-uSM 5-:s d\hV=b})ZP\B1 bCR%C) !hY`u@@8tcB9NETQG{Ed3Ѫ^MJ++" /VČGF;Vgb 줮- YlR̩B'E_ Ib`Xbfo>u$saaZɒ#շ/w6]L#kMϭYVY$朶D,k׀daGJGws=ʠw&;_֗ݻ׷C` 8 iiB(>zDqWW| g}[EzXԦï챧qެlk+f~㱹NΝrHmq> r^YAN#wbr^g8qǰrR6#R$wr?:%r 89 I'E/8ZC}LLLӆ"+a<6 qwhb"d6ݑ_Zd:R;>96y>|x#Bc\c,w}WWsuC~_(M&̓Kو΂}Do}Pfq]ɹD1#L);Vh@Ex շU hQ@(f:<5( b_U5;7׶dO1{<µHO&:y%|)4k zS_aTSUNnաX,®}xC}*rK^OmfehEI7ZC2;#ZoQL3b1XEX9ȜL͈/QÈY{&͗wlE$ܩZ*94D`5fn|ux#3ݴ1Tˊ"QNmΗXԢ2 DbXRQ#IZ){Zcn}XG>{LL(C#Ow46.Q@)D/]'OPGg0뮻.EЩr200x 6 V, h;6m cf=Ur{my˹=p}#q,bi^6L}}})2wNDEB9mV&W&-lG0'HhN;s\y :a΁ڵk^ĈO?t&k!6cǎKFFDs&t2HdT(i@.|oNjgoUQcf$ h;DC,"-7&E#"g9#qbO"4 #>jҠnD5SkBd=NFXfaz"4]IΥKtEZ.w5A! ̋p˟Mٴ[rDyKÊx`thUK{4l!)M#S⠄vL7u|G gVݒEu`YGQ;d. D N"攆A{Zju(:[|*m>fwC`K;]]ۢg%/҇>uӠow ,,Nع;~cq*+}c:PL#k/S~bpLF63::^}> C" X+|QTyVs#>u6$"f yM1o,/Ü*t8ؖ=#|596mONN~ݒcHeԓcߊbx!n=|$~~)Q^'000SO!V[I'h67 (B19ew3s1c8Dz=oBq5NTLʂTA"V1`EpOC֮]dR"?xXN;ѦCԅNYb2M~6n4]./boox>#}Cɧm"#Ns-ׇ_|RL g>XJ=l'G5Y9MѦ$uCAܭVQX< sQilJSX+A5 y]DDDcVE&D$XBቴOTuv%1YOҽDؠI -zTY,[%w=;]ܺ$ ,2tJOej<6h}J QXf|ֹ*;#}X[d;qۢčA||xvBTW#8tuHx3]w; 9R*JsI7f}OuF'Q@B|7HzbA)>1k颾hpQ080W/鵺hB1x;Uz'x-I,~MFVFƫ/?jwхX:BDHo0myq;e锷xql̟S<NC |ٞ_dD<56:$9"ȗ5˞cNۙsͷB(ܔoժUUK"k3N&`&`&`(bs=7v ~1%bhSrӦ!LvHc[#/76Ź̝/MazqWJAzZy?Yu 2vsL\D"[oMv\EַL9~ x7mڔ&<ɓ7-3'Σ\W\qEk'r);̂v>sXJ&?YO4` `EĈÈzBϐIX[k lvs^V9L? ­6嘲VM,ʲ`?vALJx#:*KTĉr:+B2+fAҫ=+>?zƇ!:]S*.e ݫΏ tW-e1SVGe))O4Lu$ S{:[t&*+ jש)8(E|J"^:w!AƟyrZVÝfbbJ+o")ZVIybI?ښT E*ץ=Ag^~rßƉ6Ρ/!}tHC`@qEy)9k甗q;NP;5t}D>'000K6@@hWh j~<):6g5UԾ8Rޟ]V)^-9i bW&c cjT|ѼP8&m:vb"al'K/4=7A{ÔA!{$xɓ)כ6Bu___A&hobMA;dv?uGlA!L$.t%?%a;]I;NpliHB./`a{r}y@)DiV:$f:%x)MGXtTcE@2+N(B\tYVELbH6tGd1.+ je&ʖeˉ"XE#45sL CJaM¯͂1LĐ,' ΤӔm:QhҀ~lry\bEb{iiWg\{52w˿ŝ{Uz,Ѿ=qޢ]>s"ї(GQЃs=G?7[ o&`&ۋg6Kl~FT*xE PLCHF3>{싧}>E(|ȿ1  K=Xd"=Vx[dÉt,rÝ>:&\yZtٟE' ytr~/ 9v|7 |97덖s<7Qf-+9BLLL`agy&1hs8YOQضS's^cB,K Nכʞo>i<S~E^8ƁzکX bfhSڵkP8hpy8Y#?h ?L+|BlfMّ(;vp?HHt2Qyboas3N&p(ko_)ILuO NDH$D"!L0 ["S.N׬P\ŅhM*h_fГ,IM)wuW'ں aXp7 ZcLSY'ef|u.HYU<' #Hm+qE<1]րv͚tM 9Xrtn9M]EL$vrJ$%I~bhU%^z%a͏"=:aN͛#Q߱-m|,?W_|&`&`g0"1J0^['ƫfՊ2HdGJϿJQ{P~_8gh@lz!yW$NN/>l3DIb: 49cYoYu5r)DБ@,sBG!G2?(m!ǰεG(ԃ1 "1>?gr( #@]ig!&Oi1 Rԭ)TMo_NE1إ̧h8SL埽<Ŷ`k<({ϼC ~iA?N0}P>V3X.77(+~x޽;Չz҆k6ELi!"W\L$K]i/hD}}}nݺaڵN&p.YR4ЖbGG30R E5x98Rb&MOc`H,8K,΋Z郱zb0SՉJʧU?HUtE;QPK{7YD sɧRy&usQ#~ ?e'tMTtTi^VgDgԦ"ujX֑u%. ĭjiLCL"q:D]#s %74c1q@`d,_T7O00XKb1A{ҋצ7Sa 3Ǐ Ǜgx jꌜlih̗ QDu $4yEu`#`K*`0;H/ :*v&:&\dΡS#5uˍ5no\>1XS.3"2LLL @O>d!"rQP؆)WMGj#14́Me(V$cdgb~h/R94h M1zsRj(xN|\o&Hoq?7˿9%Oޤ.d_ג蝿yWG___ja=A15&2{W^y%iA/i ʏi7e ;l)L\%t%'Om  {722HVl(3'"'VvŪVEe`gLԴH]v>Eg"Rgkzq߬s'uI]lRB/q&ēv`0>Btpq"*`Ѧ8Ψ۵knL7t1|Ttc\AV5KzW3\`Fd'QXCv'=ۢDu7D;tLLxW?*z~zFJurD={'}*vm{wœT15ؗ טes$tjX'b9BSD"*o)3B0)q7:XbC!*w])b 8sLLL`nX,Ķ~qG\hB1^Z:ZS:KYL,nyzcI^PM{G#rSpt1mUX+:TGyT+bcSvcs__ ŴxnHmC|"y@O+ bLhh!cG}'Q6i ٶl?L,!ƺ ޿eB>% ha0y*"0ѮX fA@M}SuW0;;VtUуѺ[4঺%戸ey&ɒ+Q\*ǐD#3hVgqyY7WqJpW$1!HE(8wB<}kJֵK$yBGmL_)4hy=]iC2== Y yh߻5:+į:ZX?^00sٻ/iK<+ߒKb:GJD`LG6 S~8J4 덯!N,"sstFX|Ml)/}yFI(L5?:FW]uU(*%3؋ MoRv n-~6U~;*0x$}1XvòЛ۾,yL-髊K(pĐ'%mk[t\3/cX+G Di WGt\7c(M+">яC=>ljoe྾FB'z:"SP7چԉiڵiⴉh@d&pamS{`(޺+^#sɛGJ1x:^qs,XZ!8p`=[K$v6ZbbrBhjSa);MOb)׵H$nӀw_ijاw,vOD)W72J{kZDiqA}<I$m4M%YD*IeiFGiT"&Ȏ:rH?}^~R7Di:,&:%`>02ϼ#yE%7f^?I wxYW^pQ4]}mufMLAxUE=o LDN jQ(nIǀ(~HA"2ZbbmAaDe&4mҸ~"SccA'!|W3SGu2008;N@$&<,wab9B1ѶX0`W1rH&v{lֶhSkJ|5RL,EQ_,hzBo.}Noi["`,5:EVi'W*E1[o'j]O|V oehY$FΉ6p ]xחbN&`G&:pN*灍;9*rYgŌԤ'B.`{k~?Bl"xvD w돹C~ㆶC~BbHΠzUE7#`tZ?"1d;Hܡך$y\675NӔwbD%khfի v3:~uj8mRyX tWA' 4R2ȪHZFU93:]KUDp>mER H+.>_~<(h9ƧcGߋzW׸lCĿXFf&`&`O=Tt㹍%A |sClْ3p.I@IDAT$t< mܸ1!Df! #cQA QčB푾|Ls\rzcw (+Q5$pD`:K~)BQ3 ?LLL%@{oŽޛJEN(a2BxY-U4]t vX{b™th%&?&}O6}_:IxxW%VyA,)f>3~t:~yk_iv RvXnQomD 7j4&`G&pLBFig{'wEk'cJ$"rqy1:wUy`!ј) 'qt/62>ܶ:;bRb'!딴>W2F{FbMZ1%]Ŝed4I$n,;Ǔ%d7/KXv9z]1oFlSDr<#>M&`&`&pxD궸hQyDD14GN! % ňt8.=@dKrhe`_>.sbtjƉ]MdvnX r+/4H]U! ,^:#j!?=}!; &`&`oL+_Jv4 &b:t$xp|4QY(&2lHʝh\n\o޸K9ɞtٰaCK>xMLLL  6O)>9r$.+;~ڶm[L.拚qӏKPwu4g4~ҟI4P_.8)(ScPHN)Պ7iC}C CoސF~;^6xk P|??k`9 F] +Wve/rYw\(cūǛe[ㅝ泙1篈\zQxrxT!V71bN XPǀv洭$6ݨtQ և@{ `kx\Կ95]ubÍneIlW:.r%O|xZV7_xF 4;f-Fb a-1 V+?uG #Wi5+b"ރG4箕םDYf! ( ###??>iļ./y::tD{5fB,iAeДI3h\y~c؆0|W>%\ba8LLLQЎ)Gv\rՏTuIhx05(ISTYX1iyvf1:s i/y;ߙ9Zb^*JtPQKT /~Й!D\kRקۈ!چr2000c%__ߞ:h&CCCQuqϴǞ'lW%8fd'O.f/S伙3=Lۅ֭Kތb|7\r\n>\L8&Or<` ({Ѳ]/ ź%jO\EkEBo.^D9t T`G_ٞ@.,)X,81+bאm(bF8nMBuȗxQk[.byK f$kqЪ`ק$ OFy&wc"=IkX;%~`==Șn@P%ܮcOB2^lI,Ћ؋0vK";u%-~pn]h>8G=QNy bw&`&` /}KI,>ZPLT Ewߋ/958hjɗBv___)7(|c|'-|008 `ŀv7ҀY̅ Fa{4z*. q3M1hNJ1I 1OiVG!˃o%\<wrN&PM㏿rĉyJu&aa +Y Fl&VP\^{%'4P^(xҨ}[/Z=ZxJxb`t,ix,Z( XGٰ ʺ88Iex?D$Og^9I %zmĆk#>ź?MLLM@(?xԳ'F YOY&ufPLMEgW^ye/i:^>LLLx'㦛n-[ns?$)LjNȬP5ueN[Xg8iO9 I(3YO<̠}Ąv%"&-Ps::Kdž+W;.+Η)!Onb4 ;Ɗ讴h( 0\|:5JUdqkΘi0+.q)2HⰢe/ u*Kxid\4B K՘講2[zmhhZ&& *n$;/8ud)L{饧YΒ5\S~"⢵ JXb(F(&63Aj_ ![>dpǾ#YvYbn#jS#r؝#3000K`pp0|?4Xm# xbHr!"kSfNӴ"'H%%!ɪLPKb d XBqH:걺<]1Eb>=3SIE{Q4kE%\*W]QZH `DcfmDAZECwn:LLLMVɣy#??!BO~m2hW,k'W8q]Dg{kun#0/Q%2D;6;>K&c#z+/H $&%XOMj Ȕf^]z^.n-E34z{עhTDT&~@#"zm:fe&o"_Mhש,e&`&p@я~_S$̮]R!≷iӦ+dPp___08+ ‚1sa^ƂcLLLL`!Mc%@wmhЎ7Δ9yTL8&{9vWePRma0V(J(%YW!$aW1qycRM;Ė}-K\Ncx񔢊D]r ^lNb8Z'v;YlH[a<;WcȶX:v@6qQoO,騤ADGuI,.% ${$t=^Dc*晉haTPEϿ8W"5XO4OVџTK[|y(l*owI_t]"q}qDyb_U$c&`&p'x"oƽޛFGGSpƑ;7Y$feW/9QxڵiB1(5Js!*y[ i"kLLLLL! 0}C6~ôxOgJ\N0Cbl'ၽK<Ȟ)Oc^JT+Dw)[ZcҞtY,v'`ijwNj)RxZvıuEOq5E '7KUS]$xB .:3/oKDt4"[A9>ƺxnk.SSI]mO޺5+xrl0EOIᦼ%ql)7E^h筎X,+JDTWtsҶS{G}lX5ӕR\ fZ ݭM)&V'Z=P"ga&`&p`??KsC˳0J}N-[@|3Ĕb9`:MK^üjyhcd&`&`&`gQ]NӲbbRV)dsE]T'٬B_g5i8RV-MVJz:Y,>. :[c}g9Vʷ{W(.DaֆVuAYV!Ŋ,:z]ZLT2k0r RJ!-DE8I=A\EC#if޷]˺x4ۼ=.]y^\}XdHh] rؿ7yڇ*"|)C"<luRI>Ftɜ\@{xD gT_Ҙ! A 2rH$.+jZpk(kDP>.4dXl E}}ѸۓpH$KHm߾=E&ٳ'EILq-*500000I=ksq?jbcJE69;Gmn Ⱖ6DXݝQJstHLnoYeTM )xF⩤f"˞AE}sHXPfi*ClY(d+1;8ukom|Y=-:U6H~mۭhݱ`u c|u{,c׳A\߿/bۖ3òy@E8.9XT:0cRt|>GY[ IuD|zJVzE7Ȋ>Nǩ,2\Sf}[LdJ-9(d0OAB(f?'008矏ݻw' l$%| &`&`&`&`&`g c3ʶѸw}p*ȄvU""uJb戰I_L&"rc)x@mve 9)Y5: 6wAe(L1X93e3]MI|JxL EףU}rQ2O;ƕ篌V/OmE /J{HEx®ܺ,Eq8؟:5)∠K4O CJ̵S|0 xx ={)r$6!3Tj.rZŴ,A9֮-|- 9j @b*ӯhއ:Ol݊01EW< A4EYJ$6C-3M |q0)8k !h`yN&`&`&`&`&`&`&`&`&`g 7%ONd؁xahWÓI( w&5KA$b-Hb۹c1[\m&y)y /"NrHn0b=3(irtbr&!a4M^(<15W".XuBqQeGi:uYJ4FҋPrH|TQ<[YaKys1ڐ;I!x)XFus`&`&`&`&`&`&`&`&`g7%SW񥡨$)T)\D*I~J1doFk#4:|tu%[&`&`&`&`&`&`&`&`&`gF,~ŃEթQzڎ`\j9+^" ' v6%XEGM!&˅(ܜaCf6ĭ-(yYyDsv+rx íH%;4h^k|b`t4tE7X\9G`tuE*u)EjUEu,-l+bD6?N.ڱcv\|:yd&`&`&`&`&`&`&`&`&pf8.*զe=Q_;1<:#xB"qU!k,T\+Bzr\9kOTV@8;+ugJt q8E+*Hb-&&X$/F.'xqW9ve;!YĴز3%!#cI!K%8Ʈ+ s`<eLt߹1Y ~1ZGU+LŀxN\N>bXr8n03M\`&`&`&`&`&`&`&`&`&`&0G 9iEM%K Uyk;<'dW1)b|bл|>pB[[ "5y" ژywi0vEj g 8BKNCbLfEqňi򄢑G# k\QtpZ(*c o,>LLLLLLLLLL4P|&`&`&`&`&`&`&`&`&`&X(^H߆b&`&`&`&`&`&`&`&`&`ݗ4000000000DBB6\0000000008 ,辤 ,$ҷᲘ i `4@%MLLLLLLLLL`!P LLLLLLLLLN ŧ/i&`&`&`&`&`&`&`&`&` m,&`&`&`&`&`&`&`&`&`&pX(> }I000000000XH,/oe1000000000@BiK B"`x!}. Ot_LLLLLLLLL pYLLLLLLLLLL4P|&`&`&`&`&`&`&`&`&`&X(^H߆b&`&`&`&`&`&`&`&`&`ݗ4000000000DBB6\0000000008 ,辤 ,$ҷᲘ i `4@%MLLN@TJ=N5G>rpMLL4niߘ{&P|&~k. Q80뮻N,#m&`&`&`/sNP|N Q8h~)bG?Ki&`&`&pwXJ. w K瞔ˎ*>~>LLLpԱ]57003; 5FUIxn&`&`& l!`l&]008p|_vc4qQř&`&`&`  qϖo00008\4q>Qř&`&`&`&`&pP|~ 9DpĹ*$<70000sswMLLG&UIxn&`&`&`&`& Z CM188LLLLMݵ6008GK4qFLs00008X(>sLLL"p,3 MLLLL#`]c008 JTz~孬&pTq& B"B6\uLLL,&±}o&8Ls000Dm,g+ g7z YL7r'8Ls000BMg3 g뺙 YJ7b'8Ls000BMg3 g뺙 YJ'MsvTq& B B\vLLL,$ѿ-[Ӊ`r2000@- g;W~{5GzbK|n/~39Gd|+7ʓ3000000000JZ]Z~J7↛7mY3:)g4xz[\S97ŭQkoL\&D)9uӝ[Tg>u)/b&`&`&`&`&`&`&`&`&8A8|&rw(Os_;8;>[_}wų$(x ŵM_3WNHuø 7GH w2000000008 P|x3㿱[~獇?,$<.(Ǧ[t:߼9NCWpKToB"|U000000008&P\P|C zQS-VT*ѷ~}Dm06mJQޕ}'ߡr.XQQRYҙ\OP5ev+}s׮L?r]+dVH&FHkY]Rמbب|߈ o:ֽ+cْq7ojo_g ~ . ,4oP(>_#>Uߕa;6?7ok~1qMC\X%# >|woww6j PyG6x0ngWu[/k([?c[b".Gpm)ps<ߌ']5?9+$9ĭw-E8+ro3*C?t2K?Cf&`&`&0X(^߃Ka&`&`&`&` -pq¡w~/͗Cswly|Aqms9u+Eħdz5bN(Vmи\bn:Hێ 6f7oߠ$(~N'繽3 MLN! 'N6008~9}s@-I럎 g]Qrq}-ߗvիչ wt6ֿp}\I7 7xƍ1wՋlon8ukjrxm?0 :yl7|Kj\y`&`&`o5{0LLLN_N{_!O(7oHr7|ٝ/nSlCwܴaN輣R~Q>Pמ^}~ʧC62wל3pW}C.7F\);-9 Ј{!F_}r;3 G{0^500Lj~'g znzG`Nɰ6ỷc 7͉? J|>7lM6_me91wƮ\ٿz8[GnPE?$ozMg_ta;iYOK:SI*INRi*< vb*b'N:霎^T|Y(ޟwYisn~Afݐ^/ 6' ,0:~q6 L_s M˛{S;h˕bn:8s{^/ΞJ`.4sSMrv b7swwQ7F6ֶO??w(ѶU:msOŮ];go^N$wk)հ%ƽwv|D@ Ps@@`x1 *07AqBkw04.Z;X.&gÃ-:SP Gt٪<}GREs/;g}d" 0y(Lޔ+" ,<m[ &AqRʛ+b0$u Yq?z SIǗ7 q+z=M׷~gNtg ͺ\7ZyE66&(.qZ$6$AP;T|F@@@@`qMYNwsMq bwk˞.9L;U=ϐ^>]ͳ{sqR}s.w};g7o]o~zsݖxVOb     R@M=aҾ<A۷^o 1ڏOz`i{mtCbi'o.덳ӷcIr۷}i_=]7O      SzhHniM" CKݚJo9w&nSʩU3; Y3>Z&)@}zߥuSjy%-񵴷r _N b_܋;X;{ο*dˋg(AF%#k<=ss;@ou  D߄ڷn -0|ҩP̢qx[7K5ẙugJr=s}X~vgϱm;Wm?޴Ҟvwz~p޹3msrk9]{R7Ճnm^Ო- ܎7  0i7!  ͌)FF){1 ].x\Vbt)bȭQ-'Zj6u9OLnѸwۺb}6ZnD>> ,3b_l@/7|P!  Ϗ@@`9=F@ |QoϏ@@`F@/͹c7@@@@ 8    O8|cN@@@@@A     @7@B.b0!    (&@@`yt  o (~g! :o9=F@ |QoϏ@@`9=F@ |QoϏ@@`F@/͹c7@@@@ 8    O8|cN@@@@@A     @7@B.b0!    (&@@`yt  o (~g! :o9=F@ |QoϏ@@`9=F@ |QoϏ@@`F@/͹c7@@@@ 8 Kheb"h-  A1  @Z)$DweL&/{ո:rq[\z@żdRv+іZ1Ie26lE@@["  4*9Ytv"+oInU>R;UyBgq&Kkۃ.>p7@@Cx>ƁV   4 }LzO|)pKu.?+nH)&=Ge3WD!m ~I(9CKTx%[z[Dwm}  ̑A MA@ xꭆGR\^]'=έ8|UJYu[LOklds}[G^m4嬐nms!B@@9 (A   0@$NhۣࣽAOV,[_M)n]IdyshZ./d?{/}x#Ŕ?1[Zkr*Oysy oE`œP  0U0~Qυ#Y~GMi𵖏x.M=ZaДd0zCޝ5s__Kd}Ѭ5b0o@@`0>áLDx"\@)/ ޠrGUҩ-9kB!g%ٶ&u6q/(AqFb+N6t# ^0>y{!@P< e oP>JióLaBtu|j>r5@jCy{_ޠxhhjIaM>[y?R/n ^s@@(xp^>YFCkٵ:*Q]$W% 9JscoP,ɬt[tjwgZ1)tFߵg@x^ ϋ Oޔ+" ?ޠ8LuYsER-Ma06ye&Nb_PsdǤTo$&@@`@k#@Po\*  0QoP"  X@@`kbdu"tAJK*edCo; nۜ<<(Z @@Nx! =X|%o|SRj}G+Yd.P+) 3YZRPz%G# $7 7(ţhq, ̅@(4k:cLHJTDggKq%|=΁KҒrdYxޠ[y؊ LF *^V s" jJ)a$OHU&so "(ZY  0C([# 3o2tšr: @1  (<űMmn 3 5  0f-C'@P!     _'@@@@@ tšr:    |B@@@@B'@P!     _'@@@@@ tšr: @"Չv@@x{[xlB. / WGE9$H(/+]v+Ǻ[z~ՊI*3Fh4hi1IzCj]4D'ZrR(ɵde$†sgVK=VR$hKْh4*a}_ޫIޛk! lo6 +@Po}: `YfISMRh/露;PۗG}No]IjyKݓW%&tQk]CPjE6R]VIW嬜( ,|=-0}B@@|}QYՠ81((N^c߹0;cPkK>|M kuo_ś~]ҙ )D:;]ՠ8? (NfO@ےLҮ亟Fݺ{k\f{\  0?7?.I ORk! TA[;ltmh4/$xTk f~"KVo68o[@@ 06H#qq0  +0Z"Z/t$37%jJ64k~9#풻М񪔒,dZ|?<(n^eue'ʝ륈QNNk|]$c]bZ[> k?R\{"ͼl h#  @Aq [@@&wgJ\4.#"b dsgVV||,Ãn8йO2VO7 ힹ%.::3,\8<:ؐfiϨdeeekϕy  ?.@@eCkrRXN+C j f[mJYds~l]Ijyˮ6Y]nHP)}qa:.(n"w/Cږ]œ|x!QO gfO&o!ՒԲ|.#O1K dsI!&V_-boΎfR-楯taMw_~Ӊua%]ܙI ÆE*ya>lG@@gp؅ , M3vٽ|To$ɆT2Kɪ/?Q?r_I|Kwͣ 闂bޥ6=M}Ōb&ruwd^>YgOo4N٧zԓ}mKyS{`%;[aSV:kVu ?RB@@W @@a })hi\Rəg$2 nZSC~gQ?;% Κ;cؼjZ>˚әI?pC`sҎb WF6޲ަۇuInOo9>̽wroWDgD?֘U@@W @@`~"՘v=z]P0z)aQZ>W+$3qWRIţv\wvֲݬW̶5U,r->6  ԝrݑnzwC[!vg۠XZגY~/:QAʛVubGz\J3' |˧YyÜXkͿ~! bb z{E s/0__A񍖞Hr˝+2 `l3v6dRw;7Yz֠Yt!ȚXsy}%&<}mڄ;j|{GKwTݭ`Q.ï@7z7ڃd'U@@ Ě+r.Kc OOt䑭ofY>[So:kbYn=]Hٙ<3~I[g6/OF~]'5R/m=U?\J )lD@@xN9! ̙!ƽ4 C '[D"us6J4@@ x3Z    LTx\ @@@@Aqƌ#     (('C@@@@'@P1 ^ Xv;  a/ Lg-@P<  0_F&@@ <zh|@2P4@\(C@ _F>Zx#@@`d(L   @x dh& |Qp-x A02}AG# |Q@@@  (@L@XT%j$*QqN]1-=fCN*l^ѕ6r>8{@@@ d!p 30[Zs <] l 9,o<$5F8ڶոH4.BFbmwG@@ šf: !АBdM3b`AqNb+UʭʼnqJF>i~\'IGVҝlܬIf}Ӻ`EUܺ=}KJ'kJIwfCTo7_z 2 @%p AqǏ# J~Q|P5l1u,&.^ڴ6M9Jk*pR~ތ$qSJUb'z?ڒo'-Iᙵlh9N9Û')ņ[rZf~I(o+Y@@>1zG88cEK@@#/ 3wO!Țh i՛\Fvf) C͖NJjyF.Gu&!{f-pYd^~F{ Z 6:0 E@(14]ꄳ0uz__`nwJB.=^jهUwWruu4~~OlԽ33{dV ye6e#៚oɼG@ m,g=@@`,~Q 1<7O56۳Z֩A^\P,ZuӠ&_rw7bhK[Q>}/wbvgb (@,>1G88cEK@@ W}$}ji epPEݺ/(v vFU)"wjQ6q-M"fYO{FK W9r G:(@@)@P/u6  #pQȇ')$yn^Hd7x2AqԻݾ.zW~պ.nNm,3r6' {fr}o5KfjI~MX;k  LAx @ 4/J{Oe-dSgNN*x )<^FJݺ{S'qn/~Jl`~/A]A]YǠxGJ_ 1@@@`œP  RhvmYK8haVSK3\Gaf Pϓ EkEyG>rMRZ'h^˷{i壔6 )GVutZ~T$2aҸI*[4?EQqɽ\)E,ƕ׶:n)wF7E/Mﴛ_   0 I(r @@`óK6u6Ui]-K"֒F9B iŢ|ݙ:&DŅ%1׌۩jIDϙtGsʲxvfz_DC.vws{ǗmwS"/C?,>H1#_S5(5jRʯk_>uzєܪ|vn;>@@@`|  @@uʾ]{fr8~#  G8Zv5C5gۊ4\5gvgں!˔nPvӈjIQ8X5.4l6'QY}gv0,'Z9h{ !  @8L@laAN+Բ|k۲\[ uAlܾfOÐ\̚qTh yKx (^7  LBx\@ 7ݠX UA9yw3(NH]H[e$҇tF:JR[bN Gά6ۭWyEQC9=FB+ҌbƨWdic ru:/ddRYZa7՝ xպ{ 3kغL2-Nbt?Ύv3D:`/(7`  0}_o'@P1 @hkEy iyeX9=F@!-k:H|;xu;Ш.rs7గ64x*/Z)C@CGz1=>@Ea @@o͍A  A@@@@f#@P<w    ̍A  A@@@@f#@P<w    ̍A  A@#b0q.  I8HE[@@ OK   ` 6t@0t7  0}_o'@P1 / i؊$VbFCC_ј+;%Fo 1I$V]48Z6'dfNCy߹g,vVV(7B  0}_o'@P1  RI-ɧzPT;5YZϞkLKOEW,}zL<]HJsF%#k;ۮѹOyӆ K?wS_Gl@/7 (ߘc@g 9-S- bRI'{} ?Rܰg#3 _<> +O5IAYAֶ;tݬAJn(m^uU孯Xw^9?07g# x~9w AqƜ#<+ _i{3C0K=ʲsyhČpA蔏oJT*bFesҙ9l:%Z.ғ#[{/ AAڏ &h^[ߢ9  ,rP=1E7(>Iޜ.װ<ս~𔠨ii{?(7__nH  O ,xAu)"InoP|AqaAHSʑU-u=Pf,@@@#@Po\* @` ̌⻶ynFuD}pyNل   _y6@IDATipgM)SԙBd|H*۸N^Lh蝈 `7:p.FLH3=ꚲ\U]D[2ĩԙr?2uDR~UGUWǑٷ1  Y~ٵQwaQqޞHt|S"{?[3Kk dd9{>ٷ1  ,Q넣ms-^^-wЛ8+YR/BzA~cm   3gG8֜3& x .}8T.85۬gg6afC]H/ǗC!U#6 @ K晳#@Q}k ڹդ$o{t?Uw Q[mbW{Q%RL!8eo]{hSNm0 )(ʬ9= lח[(:p$@$@$@$@$@$@$@$@$@$P?3HHHHHHHHH⥱ <3 =;&         AxiGA$@$@$@#`2 !@Ql%   'Cpd+$@$@$@$@$P\HHH KPt?&  <~u@ȇ>ՐvC#?FIHHH`e$@Q4ׅ" xBhm`d7z7фZiطȑ5; w4D:`^YL(<ߒ J#+mE9Hx) D$@$ O(޽ȱ7tE6;vQ/ dEqfy7  eB׃>_[Q_mH o"߄pKvS7((->'     x(-N$@$ v?[p`pthGsX:y[?=rΏ[ S<{c=MJwS"K[9e *6nc I $@$@$0E1A۶jI1ɳzڻM څDqχ;] =|܌FG6}3ȸ_[pQ#Do ޗT2"oD&%ﳛEqv?gO$@$@$@$@ (^ d9EJ|/pQ8cvKRbh>%woz̖Y[RW y16ә3"6 @ K晳#@Q}k <@R/h\S@Kz;O:$;ީnsӌsNla8#畝b.GC!#6 @ K晳#@Q}k <@ROJb+)ڛ+Ӱ[j;؁yXة)Ew[RR(1 |+NڐQSij!񡤤xWJ.,V?[IKk dd9{>ٷ1 C <\/6E"~Cy(ֳIGqۊwN8X-,jt*ӽӛQɡRʒ_zXHHH %c(ξ5IHJ 8ݟڹ U6')q t_aԼD!Y|pDq+١rˡC<y܈.y綽rYK΄HHHCCeH =cm  IbwfsIDgTӃ8ӌ SZ_A'O߳+($W$@$@$@$ /Vs|(   %H)bmA,{^a&;`YN#6v0xSSY2)mLͫl? <E @xX6}Fo<7SHS5f4Q&@[/-N4><ŒBmAhmD$@$@$@$@$@$@'@QpF,A$@$uhn/22v d9t`-7dWoXvxOun gubc'b~mIHHHHHH`>LHH G?.> BҲxQTs ?lHHHHHH (~ #о1~WrFf%     Gx>  f8a;N|$X=+uNlù 5|e#gA$@$@$@$@OEɶHH=hgm*׈l^ZqLoI/=~M$@$@$yy1Pgߚs$@$@ @ضs'(WόPH=ǏIHH2O_2Ϝ=f[sΘHH=_zXHHH %c(ξ5IHH` -!ǏIHH2O_2Ϝ=f[sΘHH=에    G"IP?6V"  x³ϾIHHH /c(ξ5IH` yQ |}}P%ku/s$@$@$@$@$@$@$4P? lHHk݁}}`(1ԃ-P%OvqMqVz1< |$9^ #@QP!  LkߋݵEz>yчz[xz6 O>iNd1!     2Y. E̪ ]sR:<\|"xQ@"@O+ʶ:/D}V"$mUyD/zl c88]feou휱I t hC4!ɘ涗R42$^֧HNiH鎗ψ7yF- ,aKxq84  آx)5u`{JqtVXDn*qѸZD)E$HNCJŖzn.ɝBNvCL)gD';Nӆs|xtƓߤ}=ػeε;wN=B8xkb19y[O҈^mwv{{cFWnw'@QyHHHH:⥾B <(Q[Qݒ"C8)zSs:{'ncd\I;~iǛog3z9p0j[B-{j14g/(yabI<+߲UdzuF;Vddͧk⁎{ՌiB= x0ժHRbi$Zw>J'V3=?&  <~h>l@)ys*Lm78ˈO07&Dna"a|wTWp0J4Zcm<Lf{?qO&r.?9s)N)ݚu"8hh'kYWqc-   gG_{=V5gJ$@+-WMQyz)HE&|Q*OvTNy4kI֖훥O[v0qlOKPX?UɞYtdpKbPSƞӆM &4&L|һ9{v?dO(G$@$@$@'/gSO  X^)8eoy|`0qX'SGX'$UBþ32ft Pᓼ oT4vxvgڢ^S'ivHa+oIদ&/H=M^=GJ¢.x![pq/ʠ*'~by!/O6 vgh9+/TO͒̉lxջy2H_zXHHH %c(ξ5IH`j(Ymi hmQܾpNH1KP:nxgs܅jK.,^yҩwf=l̝ÿ8wOX{VA}(N>/<^~<8LPf$@$@$@$tY d(^k˙ %`9æ2wޣl(nliA}(B8~\6{ p8qfm bA^X.h_5Ė=8`C]hoU=/m2mض<)}$Y5zKQl1HHHH S(3Ed3l^}ΝH)UQDq1{B<#2rtuxjL$@$@$@$@$@$P/5IH _hz>܁-ΎDqjzdo逤ۭ$o |rM]2!|]Պxatu/6۾Ӂ*|،/%Gs;O2&B$     &}q$@$@˝@o۞wBi"d{4gF`wB8q qn0B)?r|ޜ4jK҉Dp~||ӛc{k.{SƳ-Lt4HNozZq6&ں#v}+&"E5xmvV-GKjHHHHHH`y`DqvsΞH%EZ ³euk(V|Q\l/\ѶƓfwb%(6+o[]u4v;$cC'>:|1S6SaD }'TX|FMx=EzAj'ouJڍW-')ŭG),wztTr2,zZV$DDz3 $@Q,& &`0JxGw`F i&|۱)rج|ݭG4+oC;˰O|>l@]xi.B{qQ]O^lg.V-dvD;' ])9;t -e͇zt׬Jb"hĩlC궿gh<`.t{zѺK%ڰ>zp7"HU%N,HHHHH(~% R!`l>{׹Q]]joRɝ<57އ> d+;cs9MLUT@@ U*Y޺ 7pMsΪ쀔 I^/jkm7⪍GN](q$@$@$@$@$4P? lHHHL/ǏIHH2O_2Ϝ=f[sΘHH=_zXHHH %c(ξ5IHH` -!ǏIHH2O_2Ϝ=f[sΘHH=_zXHHH %c(ξ5IHH` ~ 9   x$HXEcac%   gI(N$@$|E}} ao@~zQ+Ef1    J׿~?Qw-mv7KȓR"Stk'{LpF$@$@$Uh, ;YМ& =VfG%qnt7dqRJDqjz?'FᰣJ꣱'N$m<҅ Gۺؑs~jAo׾ c\1? dbM`GYB8K$ x>a;֋L(; FdkI+A۶j3UEj?!yV]{iA=wu,-Qˡ>ڀUrKz:j79'x~*2x_R,'SaоÃ|֕$@$@$@ObMׁ=dZoΖHHSDҭtɒػ8P Ǐ#}J|/pQ⁏8cvRbPD3mB_;BXdNIO,'zѡD)իŦǘ={Gѹt׆##   'EAQŌ&~R $ P'YHH $E|܆&G&)}uiIS"zcF:$;ns&rWds!?tNgC CIIㅢ6X'    %@`bF/V$ فqF2<>7x"wiuEб׃wt SKcY8m}$5jIeQSYGpNIqkG%2    Xn*f4r[Ew(^.+q fWzy(Ȱ'%w&vΝb>.$n|QD>7!Y|puq+١rˡC<]lm$@$@$@$@$<F3xyG< P/uIH0_A'c' ]ɱ䁵)ܓHn6|DoMvgT]{ 4es=GVb 9uI{ϙOJU^ 'Uh%X޲&@Q' xRR6I鰫nz쇨X";:~^a&;`YN#6v0xSSZ2)ml9+QfS^d;n? ,?*XJ x:(WJ$@$̕-8' {BeK?l:)ץD h>II|gor'OJ~)h F{V. !OYO8HH&`"A`: dS!ܛĽaLƦ v@Ix+-CYe\oa>ߙܜYc%g !@QD0  A #[Mdv-~Pod:Gn}/2v2vC#ud`k--ҺU':[ĺHJgI!yJd8{tIB(j+Lљb_zXHHᡱi !I8! wZ>aDg 0$BiDa# ĢcƅXr\-B8W9W٢JCp!gƃBgjJK䳺Wlym&@Q }  eG ÀOB om=꫓"iM(0 _U>` HHH $ Lc)TSIBM`~G1A8D#1LLdGB qN`Fp1pN;Wk%l֗ꇊ˅d BaU|PR>#)((A,ugf,6ψ8v?R<HMuR[o>WLGa!LU    ,#`m4cetIHH`OM;͕qPT1̌WN oBNyp\CKd3J*_qh8.}U fq]"$;]+(. f ;$mE>5$IƏ45{+?,2楾Y} NH0ʽ#QZ+Y>"w|9w`jҭV#8 $ް-h7amy).é7s ᎮC5|vl^["AYN88}  X(:[5Kk R"0p5ze[w3_&k_ZG()7uZ d(FFJM!-9]eݔHQ_;Ù\%ob&w%b7{InijaIbE8bpMo-**uU.bKrF #儺6S)(wIq| t k $(r9i  X(:[?Kk R $t8KwFW//U'{`bxbWT Oq%:7xD;PV>oe8dq(-8ݕuwP6j*Gt&rZ;)#2ߐ~^Л̬J"ZHkIlb%wUq.n\qS4oB~mD W10)hb[KY¸7.*;o;/~S(kõ2W5r}lL7.yAW.$p#7qϥJI!bWRRHJ>Q^3'shX{0.~76#lWi\Ȉ&X?2"9!PW7U.I\Qv:XDuoeK(Ŷ0M$jOEſ&x :j$-(e9O  XA(:Wbr*$@$@$@L>|Oq;x!VQ\6|Jd$|1ܼEh NRPH GbZ O}lnt9-5>[#ǒv"_6S&T$&6JƢ3_|s?l(V? l kqĪu?\{ ~Tu$̨a+D<%zJ5&2ي"NLA#82V܉P|-7 (t=ڰ ,gy8v  RY6 |I*:/~Ϯ\CVE %W"fy"v:Bȁ0R\* _Ցx[GK6ZDsw%r]u}K[gUw&6{e_}](9^AnqԱ6Sy(.F8꒏%3X8.֞Xg;xnSJRXG9n=K8Vg]zlVM1}:~y3^q \LCa-Y@8 S$      XTJ7E?OoDFQ$C1l]㜐ڑӸ9PAwEDJIʪMJqm<:*VA%Pm*(!>C eü*3Xmr,1.dț\-MS,RWj:gnQt嘑&p\MW #ZDl0$z,Yyn9ƒ{ğ~盨*4Y\<^d+yu97       A ,>ey|?DDRWdJj9K`r4/~#^FNQ(2N ucF"0jː9J,+"/|v_tPD)I["֬A6]Upz֤[Pؓ7 TjYpđ aU-b/6mFBEƽI1lbUNڶ79(cە򣃫?6=zA+E\VNHHHHHHH`Eb忋֞v@pfk^Ҙ ә +3 ҧbW[SM8ӕ@xkTW 5X$h( t:4@[I.:*qg7hlHeǢcF$l< Albuw}wIv"/goX_3F$!vbVvfyS[bXfc%#!I1Vض6T&+X(WrZ$@$@$@$@$@$@$@˟@<>n◗᳉axX䰤i(H(m7g؁UkP@ܑt}aܹ3oI,Y)'%uQY~aL196+AI`{⹈ǝqo3EI5Q#mETZݨ{MQ]!UZ<=^xt3b3LL6C}X]gFVT(bXK_# 2g4N)o gU>C|Fl!|%Ԕ'+X(WrJ$@$@$@$ V &p~uSt\>%E|OӸYbKoC4H6"+_%qU*cm$= =bU |,vJ RBOݞªM#E--QѪ2)|QIrcpBQ+$D%a-al|RV_-UpR 'gF4@V혲NM!mmh(M;wGa{RKX(r$@$@$@$?n(˓ @ ?nSEݒ8$i!F1x}lt {}][ԋ56$:C$Zw',zU>F½VvP3%GhVY3[պpYϭ{JDE>a&2&b+sxϤLO9sXEoj YoVQ)YѰ!H:l]=~hܸAAqA+E[SΈHHV<$@$@$ 0~qqy\rXmV:j)̏$IKO#/"DZ%0NtiH$K6SIj -~VygPTtHRAِȖ 䱘b%_꽾VX߫!&J%i#! dNH\֖ k/%bX"%Ն)BWEF.b[hVuSd!1<|Q eEYvA+E [PNHHEgzL~cm  xƧ1@so#<@Aqf5/(D<&5rEnB}T䫒Z5%q/ԽliQrOܭMJea]RRXdy.ÆUwQ^$yaR%в80j֔BUE9"1,giLK`%}(SK؛%SSMyr*?tx:arh-*_xLi#> dEqP#   'E3=?&  I@cq{{O"Ց[J`I̽'ķ$2u! r{#B[['SKFkY$ T_k(-a-Mk[ވ^(\Q,81(+(=ƛo, tW2╼ P+ta9-  b*D0ÿo(i'5f b-k _I-D<Ilܹ27lJ{I;GxV4Գ1g=#8\ēETs %z+Wp{pB^Fšp/;pWl7_BijbV6S#EZDbNyX+Es]~Q"9 cF|M7ZSu$ P/5 HHHHHHH9D^Ɖӿ &HDA<'Bؖ:'qRΊ$d6b-}ƽ% l:k,G@6~uB"EJaGX䮖J hoX$rJu;. r DbS$'蕳hYMhbkn:F '!$]o|+g2NJ%`%M7i(t;3-)-lͶVv,Q<mȻۄ] QJn_:(^}e|^ڵk?Pc(~ltH$@$@$@˓7\Q LӒW_[\}ό&6DlR֚vDq\J)9hˎ(6erqQ^͑:ݶul|֕^EQA*QJ\6\>iCA9Ƿ t wU"Dp<ĭelE&e)S$!SD"mal5ڲıu6菉.8Z?Z 2K044Aߐ?.]p$7^V|[ oB~~vF@'N#e$@$@$@$ P/HH#O]=3`F+kHX[׾kq,e#~)mlE[RٸI,eUJ շ抸Z[b:`uYsp{06[rU\뽀ʂ1+ǚ^҆O,%gpOuLN7ɽ*m!dbSg%t%L adU'}J$-˪J®j’Ѫ\jVϥ KT񟾲SNEH4""~?/_ޯ5Cf%dPn|7qge8&@QXHHHYL<ǏIHHI> rbCHאJ.H4Ae= ހo '1quTwnmŧK,ceyWREl}..%Ej D-EjƢx^ZMJZYR|nW[lՋK8#گbAՏ/_ԯ|v[W]çg7a\q0JJ'Uo0c!Wr܃=k/ 4>")400&\Du Fo^\3q2U`NVQ˖ϦUbWIP[׌"-YdnJ$Վyı-Sj_g=KWhp?_'_{:;wnܼ m9摐9Ea$X]xʦu3X%_8lX?M+٦(6˿PVQD.!&.Z簶 V3i dDU,SE>Z [](6ڰ69cG"UE'epuRRF)+8.hMY_Uw ~#"G~ܾ}GǴ; ~P|ถJh$$%D3Dbn܈ə d#l\uΙHH9˷cǣ Gg$@$@$ LL׃_W(VJh`%Ml\,LSp ~ a<.ciíESr+76uU~T|=o`fMϟFU\nBT_,9%cŽXGǪ*:XJ^gZJT)ύa-SG8o'x\x-|q1LMM(##O1n!I##eYBUpk;)_RZBQ5`$$ ^ ,/qs$@$@$@$`ٞ4D"B\%v{[Ug0E)~mlg/I])i'FPTijV߅b>9Crj$K_"P!#p »Hq)LiIaܺu)KꙖ-gJ@ϊ6"SEo%dlwvZY XEp :<C~C=h4 nݺ@`WE _ 頤"XnjVXEpH_4XH߹4#856.^,E"A < W࿝v"秚0#mQ"}SRQ<4ؐ,HYK8K#F_06SSıFg%;6mAq7*$d2SMd޺N=˵ڐŦUBV3rY\-z,+U]Q(Td dYy|N0SRH℈buAnb_'?h; bRϝ?_5.]NMDGQyg$|FJOt ژN]+vPHT Wr=e+(GS#3`*GN=CP?_ $p~z`:,L!0k99U!bY-1uU԰%VrY(?GK6KˉK ?!"/rXΑ:8Ƕ4CW=M*ܵےXPGL}͉RYW4İՆu1Lvtjj*QJ`NF/|ǿcZKQl(^<+$       (_ߵ wM &fޜ,Űf SmY ]%Sh[| M3*se:վJ3ab#8%XީCDD?? i+)xnPKj!,o,R*?"zkyk[ؼW,FyC c>uzsuoavBn}t2Bۄm`US"q-+,RɘDKp8q<7JaDiQ*,EAID{uCMK4PÓG3[[9o>E= 0p$@$@$@%0:>rGղVG[VdV[Bɔ{oaId-gIdC.[SW{n瀢"3rXA,jX}%x2!?o Y}2Xk,*U6s2YBJІC1O]Br\0Sh*(0h-&e\fL4ظbw!1AR)\qj ]T G#Q,cr SCRS8Ey.(F^a9<%t \<祪 $ik ՔQ\ROPk&pg$@$@$@$PdHH Wno?se)- D%*xV{V9g3!ѩJLDQ0_16NPd#u4)Ukxγˣҡqi7rd#H84<.8$UWIψ Oڍ(JdKjP"TYKZ<2ظԥfKNNM9WFHqDoUD9)1ǡK4WMOMbJsr I?UX$vyJPT^Qj \H4SXDD8j޳Dq@EX(ųP(φoHHH($@$@$ zHb% -kDZ TTuDpʽ528N}o붤ٗQ^mvUCXzSF8RVS%7A->@NZhوVqYPѨC?ZJY$ ARLqoo׼XWW2#!`V_|M` lZ%RYņ%}(祣rJ:˨maAI0AB$2| ֖$B{LKgWd9' sJ1pp0#(ۗP`zQsgrtNM5{7d244t c^~f](,*]Z".BCפMO\{ nnc,)H5qʉ_a-=*oL1W_7dh< %PpLM#aOHr D6 ?Ic{,݀s >p"Br^ :AK [* DpVIzx"g{#]pwA;УMc3,ǫ`>⺂bӒ0@1x:#-F-])x3p\Wpn9tn+qv\WpQ|~f1_K&P}Vpza|R9)Qnb`;~F&vvdJZG\.rqC2.m>)t{lj9w~=)wW]{s_,nbRPu$Z^;sT2@ig5:)K?ڛ2(ۍyi1lHsw w܀4_~Wv[иIx Bbh2,Et>%<6#p~G: ={98}W4|Y\svvL#=<8WG,1bps+$6݀Yx.t't+c(V _zL W/cCY)6 Kٿ:ڕ(F/ej~HG"[Fbu(y(uG6]#M2OηeQ>.NF}L -"lq[T+Y<70`bQK ZyUJ  t,.tt%XCI00Jn {<]ZNPLqIx(>xoA$ + + + vX^-_{uMOi$VbƵ 8_s-!ņ6Ĺ4%ytuʞ}شw椁p^<&SZ{P!$İΣ%!ixūKwM}_ rI&_V̊,͗n˻w qV /ѮP]G*@|fҋ=}C8'`' Ø_H̺\Uu`yĦ +Q]av!IrPm=~\;(|+ + + +pS8sqA] oIB,MЫ7` \y"o׷[T qmUF?-LM'T_ڇNacu~u)"],5ހ-YhT#2}hoʉV ;#Vo^G1bUOt̲U.QM>"K L!Iw8}9(|U+oW2RADI\`4tP@LK(Lb=dnG~ןtڤWv}26;Y€^`kω!%} Cd$s(zCZzP(^!QqA1:pP|byWWp\Wp\Wp\WpnTwA՗$O+((& 2ލF`'&N\3xpU-,gOJ&?!mݒ}H=w[Lb,ӅcPc$ڔ4ocqiGv~ڦpL/a}S6P<*q(ʡt~ !'p(7kpgڇ+ҭR|~GX f$WGlyq>[N1 N ws{'pX:8`^2c<6@1ރd`GP\"sJzQ|p;5oM' + + + OKKxex ]yo%:ݱ[vJtwvC/PGHhX u<`oya0r (F9 9@2[!ݶ ūpb䂹d =qbpPE+ + &^?0r\Wp\O>&/~+jEAeKmt@2\nUН'r5U2 Ĉd6,-Y@lާ"]l,I[=TJ~$#p1N*XeabnK픞v#f2/hkQ-$Xaq%x u,cwGK`.^)5\B㚔tJ'P]=҇qHݰ @Bo8cX]p'#(&6Pl.$܄ַb,ĉ<Ȏ1 4iQObW@:Dśͮ+ + |pPQF}\Wp~So"_myݳU!/,q !1/apalWn |lXPۻJ20v~@bu C@Je ~rq ,"pE2;u ifY< ;%۹G֪Ӓ-ug& ȞLܱ5+,V@ [cX [j9Mgt"+*~ʃjU܀z1xAt9jwt=C&vC:r/0 7#e &{?!z1qG*U|b:yqvb0cA*xS+ + j 87mO? + ܨWӯO]H9q0BX%]6W1f*eY?K$f ­y]A1ܹۭ$2sFd3o? jYzaQR,m]@; xp2u<%"ba@Е\L+skr)c=8``TK3pA/J.M| w GGe}oPۂ#.aeZ_ Sa'p@^(fbS$Bb{r8̮Q|;?ؒ$wr\Wp\[I_5+ ?O䉗Ia =),!1`N =aU`ʜL gޒ/UygA"]ͨb?5dC(_ k{s*Łt{MM<@1T7Sý *52`L)Eryuq&8 оQom<]ɥ䓐2>|]:s3V۹*b7&S@csyTo LL@0/KhX3,E;HtuwKb %|qNN-&m.G`P˾0c8>Y u'› (.K(:k)_ףQ+ -[kE+ u*䫧YI)C@1 $3l ^& ~KvBWQ cƲBΐ0yk,٩{d~ALꝖ{^E)yl@yryb qQ{/ȎgqzIt+;țS@r\9efSu`={ܧ% 78z[` p ր9} cm 9_R7c;CJDxĘzkiHRSXW#(^s}uc 8(1|+ + DDѮ+ +p '.OzBqQXbP5aYBSyY Яz(=eے8]3 w97cQYm?ġ5i`J!oTRZ]Y@)=%9(V|}f*EnJn6/y)$Up]#!$Aau=L*M]kW*Xn9@t@+ + + + lS.,oTP;P8  "*GَtOLJ5< pP8@__lC Є6' 5)ᎮymkkCh"D]E^hB/Ku]MjpKuMWf8 / *}2:6.?<&ph 8{vS#r G/a/ƵkAtx7qQJC @\; ?&T12ՕzbxcwO]k*x+ + + + sŲ_S Ǥx%^c|b@O3sr"@I>8ƨ}IS \ 8VHLlDc9eB/MK K4F}@ 8 ՊTUk\[AR /%=';C{di[n -hUG0.sXx +6B 0rLǹѯP\(&a'Prp0c_,Fb?-\Wp\Wp\Wp\W؞t/gߖQ%~v]U RvA((}Yzvaz񇙦˄ʼ4ԄbE:plgڧf̚.ֵֵW8@}P@W8-SܼN0s/cMpv9rrɑ#wH7MXs #%UkI [|`-j?sRI;13AoPuQozz"EAOnP7(s\Wp\Wp\Wp\Wzʗ(쇃&YT0v d tӰ8&tۢ8 5qNCNbVe2j/b{*j=>:e56n?4۪25uA,yE8`(%==84. mЕc[.^ns^Uk'@d[T?Y׆:w5^6{tM5 .N`cc4(kavO<_\(*x+ + UTY/Wp\o)_y{237)p"02H#(&8yrXwFLoHoY.8΀/F8ppz_4@2ݐ*]1'EPibdǰzAcDEd$)'1@*qmELΝggI).[vSKShP"ץy[&{]9?"g>Y9r2:2 05WU)㠭B`9`oH 2Vps(r2:C_k}0o[ap8?v5}|gb!Fq1wD%qcap@:"(>\PPlGGO= UÄ e3`m] @!2CE {Xq1`> 0o|t#61N ,5W6(48: =^(pGqœ)xk:y/Wp\W?JQp_3 + |P iߒ/:wԫ{{G8 @V |kaaL[^\gYڲ8ҍ6`>ƗKC ^0y^q?y 9Ԥ3w ]``{=5O>!^7&>P]ĉ6ԁ2C gS05.@^s>  C86e\ɱĵzM!1P:~3L((^av(>1:?\pPB + 񇉃j\G+ s򕗞ğH){Q݉S1~BN]tަasM,/v-(lW)hlPVա̗Lק0XQȱM#pQCOO]ePL3_Z#̍E`l}B_nax>GH: B{6΢7q 7KӨ[Rgbvkb(>o WUAUWp\WUpy~3.Wp\WLAW_/_h;+mvp8lg1CƁZb^d!'d;>t/ȼ.u@Pd kƇYbS`f]lY@IDAT*6|{Q<Gq!8OxOpLvy8趍.^Ba&fbF8ݽihl@&GX$mYX8k`ߞ2c8`:b5G$F1?joE% + o + שӿx[~MN|Ogz֡vMQ 9 ޚM8CGqd]ld7DPzB^Xssx'6zACXj-^{ﰂQ|LOo1I bΡ][u i7Bg7s8@b}Dpx: '3&Ot&!.1 [7yaviPb;(ck 8(ޚNp\Wp\Wp\Wp\D7NOɗ~SuKxDc'bV5W/(v-I%?\lgQʬ[4o֏hS+5 aRMx;qHF(nNOw3Fx=(UGM1Apk# &4!w Vky4VnG7Or+: >G rqR88PÄADZyٝ?m1ٱ1rGW[QAVT>+ + + + |@ ,?KR/'v"@ pF9 X ,J8tE C=kcX/Rʁ 룯3!֏,kCQkddjo /^cٱsX#q+č08`[w沞cüi8"~i yն4(nϪa'0_!``Ցx;IQL \śk=\Wp\Wp\Wp\W '~Hz{:u᢭bp%5-8y2"M` (H&`PmyN^a :7bWkU퇪NA#(Wh 5?p1z?p@(f@9 WWAյWp\Wp\Wp\Wp~- [_צҔ4KR,,۩Py\ڐSp&.J uCvG oc?Iqmv:  ;3-G1 ')P`\Cn=]`7 7ӊ5lw=fVy>s+[X|ƈMx]t IuPJx]ݚTq]A@vśw+rMp\WpnUrmb߾}mQ~TY/Wp\$W .. ,H}!(,& ^cX bޠ3R<E|ybP1Ec>ZdSmI=3?bߤt;뱼x@.I3ev1A)Act$bsek8ܔ7q.D68lq>!l϶z듸96 .&: @LPvTDJ |M;zzV6m'S1(>OF=uk㍮+ +p3xg _¶3?mQ{+ &)Pf叟w{?<4[~@È(1! ECYc+TF( L(I)0`3CPXP7Im U'i`&`]wm,w/c30|NAq] (~!(P.tUKk u)PkYowݜ9"x6̝|:1s= |61"$#7X Cj388/&(av)_[PAD.+ ++@ a_WVAuZWp\WTpa*/_R˿'m^>X|ȜU|EXbQAkJ67+n8]LO+V!w '/.PO-r&ՖT.3(uY4CBbvQ [!61`l*"0k`#e} ka:7)s^S˱뱾d X9 Y62˵Z @ށS 91 ]1[_b (<},iTGb?-\Wp\WV`;bw_rP|m}p\W \ſ 0-iT L8pG0Q@b'UI60Y|`od^T/a&ծΦT>c^&eVv'㳻ŗ. =nHC_uC;Za\bB[i>@Z7lmm#L@cHrc{oωEaMt,֋IŬHCagQ PSP!lg(Vck 8(ޚNp\Wpn7*v7_5+ O]/|'A"k+ ?q7Hb؉E]Ōk9u#LE1-Xio$T(ۢ0Zt[A'>v+v&s2GPފQ3x@(`WA&"800kWXen2onp#,67pqN+ös혷zl`8_ʕba91mZm'kp(VP|Ő˯-*xBy7Wp\WW7op\Wp>,+/ɿۿפz;;Wjm{41Dv=.ԧe g$uA] S070X6/Yܸ",i:6j?'FP<7g110TA1{`VЪk-*+M_*pa#"4oחC{+.1 7/_:擔u,o|\0zBA1;9~5oM' + 7Iq&}IXWp\Wؖ?9vV?eiTJ=T Lp0NP@s]mpǡv,0A2Ytu܉D&V7q-{}krBb_aLd=8@ҕ%إs8:/l }a0 \3͝[p'6@8{M .q!' } 8BN9X;2Fqg(( aŪlM[{+ MRz\<, + o˥iɹRi}+() !&1Չ09d&ĀmmU@EK m%%;\p ܊S@x#XRPpX&$(1%El50bB^c#f#$^Wv_GHz>&8j%9+~1qꠘ'n;|X|I>/W`So*wp\Wp\V\&ߒ?p\WpnTBs/򽷫rf8EeI x5CO(F[;a@8paxֆ86_l4$X \m31_2e6ovK&'Gԅ @GqQL:P>'oQAbzte֛뗩c^s06@ּnZb)+BbZl-Zpk%ediG(Ƴ,FayA1%k 8(ނHp\Wpn[q~GtWp\W؞+<~9 AdVp*,f+2cg-X8$¤d:V58i䃙p rژ(1?.(&(^\XXxǎf_Z:s9' nA~l&Nb>CAGИ؜홍F]j7~[@p o6[Wmb(')(I!Ql*f 8(L!ow\Wp\[BkM|K|EWp\W؆ P6}ޗ +Ϧ0,KP8֥G`Rs&s"kabԷ!DTIgYWq; ^nª {v΅ 2=5%N)c(RrG0܂1bkOt k] 8Bu!*ЋX #(6k#Dnb<_͒m:xdS KA1PlB⧞zJ+7S]Wp\WPZbw_/p\Wp>9ϝy̕g#!ԋb8SpFbt[{p%@ Yfb'FǙyHD%P] &מ VP(Ib;S(6HLK ۂE9T[Xk0"!&Zaf3|nMl_TM0V7"P 5Uf}؏ kb8A1Ek+ 8(ފJp\Wpn *v75z9dž_+ +pk)pa~Ug_~-_*|jaM '9ϺαIX`MѿgNr_ {YN{//%e Px8BO|(V #aPVn,: y0A15fr'dn%(:@b1osG [RAdN+ +p(vƾ7r\Wp>,f+%_J}G& &@;BVՅr[eΣ5 8[Fi|Ct3D!M{gesxv,//'u|OF:P` @P}H|INڇlKg)cʁI>OʜavWpv(Vc+ 8(ފJp\Wpn)*Eh+5+ ] ι-A!bB(&fb˯-+xRyGWp\WU8r]uė+@eYFZ\YRԤ Go?$#?>ȇHs9(Ǹ+ ??5's'ezptiI ?_:ˁ0!' Ӂl#\H63XoJ{Q`@\OW$-eׁb(?e#UL8l7x"0Ecs 9qsicܥKPxy:;PV x1*v-h_!lsm|bctW"O9~ZAMY_/^gmNPܝ($ŇqO?r6UAyWp\W V ԥWS_W{Q7=] \Ik$AG p6U\.{YCna]Fp4JOw'\k + +URkȱK?晅-nw%)&qqX)&H8dx1W%6CHBGhCj8,Y(ssd+ 'O|ZcwvG73keu{=5< ~6XP9֧`r,[bOjE9pH+ YWևs9XA@=tpPFp\Wp>}F6q- Ɔ W!q͜!L06eMmia d3Hf֛y@_9iYbrZǶ|Ns\nٽc\G0Gq7biwS+ RU{_]MwEJG] D&4Hpt!KcTd&|8ɬkz\ Q%0F>8L(&¡@ Qwa4Zlp\Wp\(@9R/pJL Vϔ(Ct#f+u\ F/dMcWv58|U\x0q[ٍ"ß Pܡ' 榁܍`W!0a֧\ìӰ#DN-CoѤPޕ`0$I e Yٛ Z׿)B0g9P|Vbbw$oA$ + W/&ϾUpk&sݬxa3ohcn lkf*揓,A123%HFЗИQL1]*dX@Y\GZ]{]rmt-Wgp\Wp\/,7vN"B2Aq G1Ӛeϊt>) y87@q\%9թ[{=:zSrMH`0-(!9 ǽAv߂'ɸoFIlشWAˋ[CJuqPsn~6saPk5Ԋј<[9(b~mE[Q+ x⹗i+B&91!ql8(NnsDGax 38tW#$fn0v[mέS]]ݥ1%4޶>+ + |l8u(O|R~!5 S-qc͜GLoJ}\ RV%r'Xavx ?HP|ҡ1i, @5q%~2E,V}Qj8f uF'1/Ln{S~ƺV;!Z?θlku,svWf@;W+Zy^߭.Y|WBtGJס˻+ +Aunu 6 VH-Џ(W1S8(֔X!1[؜xl7hlp٠2zYOHm+2ўpCi##8old*J +  qbIEgOknNjtI%\kMJGwd-[f^ P| s#=5pc;筂be:L8 -CR+0Fhtۭcab] ,VP0 0XS {ȋ#=w!}} 8c~\Wp\WLRMykJ Y:P+>)=tߕH7.AC?]5Ӎ3 //-I1 8X/q]wc8^5Apׅsnc< \.*pu=S^#օ 6ڬu,-pf:sVWxnvJf ^BM܋v@É Goo)~*xS+ + | <(8b1,nc~s`x@Aa@PL"j``:O s\ z,J]|V;;eO p<24YWp\Wp.W{]+պ^?{@s%rǥMi%𷩰U,.KRMb*(~r~v) A/5 (VK۔zч*=X혎g3 9G fUY-pCuuX*ɮ e}5E^+B0PSXʋґPoAJp ^iYA^kQ{Zˁ={h_p*\Wp\W`[ |W~)(.vuk1csǎ>f?͇:(mF]cL0Rљ\8N3ژ0AO<]1¿a-aEa"? ~ بݽ N㣀ȝu + Kω}SU֩<)y)٩n~-W>{VzޑzJ=p !}:Gqqcb"FqGgA{8q#8 s?Ir@Ў1%8WG\Q}l~Z쐪<[Bh{SsN^sj+KW!=YY$KsӲ<'5!D3s!@_]Ï<Z p ؂ wq\Wp\+p-P̍2;5'Øx4'@%:{Xh \ ,F ~#`&@f_hRq>:yoM* cvw0ٳkL _+ +S@] < @ kHFtrj^~trAVF?H2 IyF9i+V`赞NAOw Vt(mo ?08RnZ^7*8u 4^[-3鶑0W1lt?cbg͹:bJWW3x+8nxbte^Ś(^0n瞻 #Gݻ]l~_f>p\Wpn@<M]6Z7X+i,-vͲS@gu3 ynu` -D`NXg8q xs>Hi?1pu󇉭=P&vH1 0~+ GW ;Vk$^nb:#4&,c/@8ey93_,NMν(}I߾9ե-Ɲnol:KB(^)#nQwއ>vaS0ʼn},ݿ\G(vQ?ۯ٫^a0% ɴ ,0@`^֋pEY@\48l2pucc裏}Сk퍮(z򾮀+ +p l1 u &Hz@q@ߩu Ӑw=f:C۸sv>H~ÏBxTz>g>-;&5,CV06_+ +R{u`$f]SQP h6ٶ\ʏO,ʏO.[JRa=m5'V*YY]ߕ ~b8cլ(!r(&b%(GBOt0;ssObAwV.(Ęı;WVd}"+=֕QA6 ZjYǠ5,_Bt`BX9Y4u[L}^|>%N]?n37 *gs\Wp\(\|95`4Bcݜyg <a%Ҏaw!(ؓv<1ٰkwxC\cH0;n'yH8Jz+ + D{c! f(LNtrLWdi.395Wάȱ٪,Vʷ% '@ =){$EuqoӌdlZrz %?42;[{0X!p/&qQϾny't@b|M6g&`_^:ZEp0]\YB..5|%ơyp7+ + \<+r܅l#56-l.[O c.l[sr󮶏06\c[89HW9/>w1l -/cT vG(0> Ÿ&a,cm1F 6` tQ0wwirb޵S Tr\Wp\8{6ޘUBPl9$0 L1e],}SU99_3 `_-62(}dW{7\*8.(fG,15@``cd`8zm7g6 ˫%ZЦ.lŢ٠ul&ֺzm򊴭U%֐BfMq(]$kc"^=Alc꒽c=nمݑ;;}opP!p\Wp\'1D𖀔~nӠUgP8_#XyetoEܧǢCBKKk@7)@~JO<8u#\\L IЈ}8ЁrA9ovm2 + F(pt 6U'qp  F8Lpc cZX _]i⹲LRöFq{SF_Ѽ:؞F;arVy[(&(;cEP|n86VBZ#uo{q\ '1r$&.,bj{PmZ*mDF [:\FK%S/I.9)./HZpAav-) @v~_&&&4섭?]GWp\Wc3/!O<<{5Gqk#%h[ ruJ\[8n7, lGAz3xay,䏃eLJۯQbu Zދs]8 Cc\0zJæ^Q\Ob0ŷPlX1 [#}cyj=/[eu,s i1bѽ%SH\cU<'.ܬaq,Η`at;_܎}_TATߟ + |x_w~Aqq'3q*mRqsj艤j븳pe:QPiC?bhWXuV٘s_^$#TXb ?46Xܷ}='? Q u + r#3`8^aok`AcʭƘO2c)Jk2[jtIdrEdzFNe3ueZwt5$wm[L7:$7q+(ަrmzWbLZb!&R*@8xچK~-vw[2kg],+\.Jg2V[%Y{y!&UwhG  8(%_+ +WGpY8v6rjyU0: #m *\_uԶ7 uMoyrl\.Υ.#mq͌YG}] Cd1!W{!16W+ + |x p@lYOxL71SBaI -[Rf*FtcmτGpTfZN*ͬq;#MF&%TMMm.aDpwZ[_ /CSPav =q}pk 8)L˨Sb\(8Bbj`[*S1n>KT^{s7UT8&^$K? m<-wuuKŒCß'7>=OƳo + | (fWQ}:NZ3:f#U~'X?scu 7XJ|7 ụ?= 'W:deNs8[QǺsV?{nSeϞ=n9xƇG~ǿ+ + | ϯ C v)&(&5a%FBġ^!1(sxP}>0cwٯ8dn kRlh"` ̭n_BWN:l_26G18G =q](d],AU,f>[oΌL,s\VR*h>&yɭ=[ѥLJq 붹xFE6{GGdlbLܥ!6XSg{A<#7QvGJo/— + |xշ[ϼxUP`\dcYn'`9g؜(3@/?3~8<[j&!6ͧ~]_n>:$R}O}J~!90b1^]TFp\Wp> M.F> ,6bs'-y,Ρ`؞e8coV 5&?@xɄ[kӘ13g"띗z&q'7j5_zSzVO~l楠8c#f\s_Gu\EY*\Ah[(uV:VYZ\U(39 .J{[Ub %^\U@bBdB_ۛr9CC#2cHG{9y@f칲Y E;nGz:oo + | *(f[.JZh ؐxlcz9agq :Bc2}>SksYNH1+ vCl)I𜤜g͗ sus?w ۷WRr9Wp\W(@CO]EAdPi(,:u.N9b^r4bw]G@IDAT]v]XP5$k%8]ǍjpN"30kH8 +[>!Rlp(b;\=Op itb:9e3"`PZJ{i}j~_}% =u}ﯞĐ7@&qq}Bڨ\+̀ qAz}; \,y% j0܏09?s+M+v-*znQ|e_,..F#.ٺu,[:S)` )p6%8 ]2-z } z%$Ec7M='7]-˚myq>hc竮. h )` \ |ootP x. [QPL8Y׮wXL1C743; wsfry#t=~ókYf;Aܷgsk⫰8G+W~yFigÆr-h110LS0. Y`Q3X0Z%a,ূb8&(vu/&{X.`N8s9pe3O X|L* ؖ5Q\UCP Kε_P#c208Due ΁k]=W9=wc躏s{NO0%0wD Oxxh/Y`{v-+""^Ǫ5Ҽ%=zG+ Dx}~Pa]a)p50P|5նLS0LX9P| #- O]#@p9pvePƳs>}q| 3~gIn_nOxe)}??Hkt &m8ϝ3sZ >xMj /ol9>}(Jf>[MELAK!(^ijI`1鼸 ıx3Sj'tEQx hTోv;4vԠzBBcj$kXK10a1Egǜ%a2w!MXghǝS@F2LS0k (v ,ұCϸ4<@8& 'Sz,@d/O/p&h?VP˹vg<~^ϓt7Q{?wve~0&RaFnfk,e ̮KK>zHҵz~44ԇl )` Q7s o7`0ň,NXK*!kDp&Ś'@g\gF+b@A7 v@aiʱ YwKݲo]`;9W>~(=9bӓR9ιɱ4v~uk>N.Vs qú]t;W,kc`K\,&"!ژg)iR]])VWKsBrS)p%0P|%յMS0LS R`+z#'2^EnQ<Ţ}dvкv7:Gðn}3ss"Iqbnk;gP g=ܸ<c=?w7(きk\xbq"[}dʥRܗKYy+óbsyT|f)` )p:&DP"| p Cu,s~@w(3=a5zh5 re_C_p ˌ#Gx55?:xQ.&Jaj/,:/vk]+iA,87;)3)ń㒂1m6>:p|w7o[:0"+,jYMuĨbm㣇ǬrkkfzJJs45Ik2x0~avq%0P|%T>MS0LS`W?|PL 7(Bϡ̝)EWB;UpA`#qeu\`{,p\x99=[$6*s|yD Rl&JKx)-%&.{Fb62a6+q|0.mΦ)` )p VqjИj%Ѱcx  &Xu D :uQѵ Qq1BY>t e FA+^޷2CEJIe4|$5aX ΆkG 0)9$[raJr/(0&x:G ȗd>A2gׯt'@PYU-M--R[Wq׉.2n~6m\/=KmMTUUu (*Z)` ) ^8#\]97#2\Ҭ۰>qpWy>}ĜH6| cqszuWL5,VXPP_`|x oB$4G0m iD/E: =HfFcՖB!+ځaد tenVxAOZ(ow yy(V)G1cn>"=(Kb\z#E= J )ݘL&]m9Ob;F4~ 3^|***Yֺ2&Wv(LX, l+~Vca \n _nE?S0LSȪA1#˰m8b.m3kY І ^PK?ɝuqbJcG<v"nO.A^;``/&u;h2Bdc _ԍL"Z%}|BpVY~fe-DDUj劬ϭ0LS0\OsX-!<(f1p ~Q Q[0RJʵM{B(_6q˹}=#/?`s T?@k!p,<2ԥ$Wfdr|T?LJǤsholk$⡞{9: },ȍ1jhlp\y܎}c F#yţ/~Q:::B5;MŗMJ0LS0.~0 lBsԦC4P |b7*vy͒'1/5k>,Ҟ^Ű݃Q„ 8 7ڈ$]xEϨ6<$y}geil(^j奪3S0LS p-B,#\1#| # ״pp:Z3\?k41Z` ,&Fr\-Zqik.ʔ|o5.qoA9bGQ@ql(yHL7=9&)a5I|S)J階Da=eSjrB^ (%^<-}Y몰ފ-Q:W«8<׾u=vk0N>qfΞ-G02<$'1\[bP'5Xa \n _nE?S0LSȪ_(>Aalb4r w/(SevtrѯXk/ԟZThɲ1Ct>48 }}#Pܾ[uHR|QLЅ\la#X7Z CbmP,cei}p\|8A\(ϹNvsЗn/*ˊe]2\:ݙ)` J*kJgoʹo99/Jkkl۶M~~MZ<Sr+`r+j)` @V? ϋ(Nm87#}d, 3W (s)>ǘ$ 2n:3W%H[[ ?"?XmX%MmRr|hTaa_2Z:WK|R|ڗ`d~O|%c@;n$}G?;fED 3}f_ɜW3yfe7ܼEo, i0GyIt5ɺ]b6LS0LkT T @:kz'\h;߰R\_ҲtH~,5O?ʟ뺎byD 4b@[cF 3is^ѽN˭cOz#E[WaN>zdڰ`aÜC\~;Lkn(s&>sk 8"tM\3E ߮e˖!? h壯YS2)`2 iݘ)`  (oy[OU FÁYXk& /6.y,$B[ɳ#9gwߐ uy+8(ǎ6Ͱhhi۰VJp"g"uO e|~v)eF~m}ǙjlrJN[lfdn҈po ⮶FYa)` )` ,Z2$QQ.xGSn,Yz-uQ8].8 zi; E#Q?Dغ~<>ZXqx>w3WS(hpo>#zP𺠐I]`S70(X1s^;5\R57͛Ц8SO7./֏)` )` \RWz?y{b-b ~6 AeAmyK%#g|Yί1On8adAǢ Q#뗵JY}Lc?$?/<&++mrq/b#'T0`w³>R bl0pcN=Hw_UdS>(`4HIy 9>[rMxH~%gMcPaZS0LS+k 'åWrJqƚcMj4qcGCgvƏT<=F* e·M㺾a)\a7_3%qMp]()@r#r8h6EЀ hk[.ۺU>HGGGx;W\W\b0LS0A1'zSA8 lY Y4AP@4<eOqO=Mԉ%h}UA2G!6 M rĉ5HyUnq.b3 2IЫ׼5m!p0-fc]|\?.(gD27S WIYR9{0ƚ7mne _J&ތ2aVYV=֫0LS0LwG~oN4r ya]p _\2X, o\~va1(mlgdFbs\Dq>%]p>wQ>"x5F 9kdrY[1T^oi}۷kLgkj ()` )@. =Un&+3w:XC\>w*ն. g+s RM}}'cPiT,\|-qbd rn9dQHc΄H(f~.jXjWp㋰ֶԄeؠ-Jem<_Ca (L`"o*KKdmGlhMS0LSѣ|G^E9rm(e"eGu(qP05'>C$0/} ` YbmIbI;`}4]*fѮ?r٦O q5x|rXJO|) RSSsK+S*+`* nÙ)` ׫yH'G@I@K;6 Q OcT_jk]=vp2(._%S'#[]$Kj|2nq\:/=U`A_z w\LP>1۲/jl|ZVʺF9P̱lOcs- qÕ*/u=?i{oS0LSx C?|@j 6ׯ]^C992+u`׃&,,'0MA`\N 7EKL%6b=t+ v8w&-<>wB`I`. A*7lq nBKN.1Y++. ` )` Tշ<(>zƒ K 3:|Ze+z&gf-Ǿ(1DP(mjETqZKTWF^UrC9P҆e+g]n<$3nv9 (&DV`q.eZ9uW#k7nŌq#n2!17DU%aet 0LS0L p444$;kxV@5nq=5&C2;`k^P/G> }'5! -#(LBظQ\ k4}8N&=ֈ;G!u "Zɋׯ_/'e9 1kas1LS0> _(i`5 u6 {cm,>=,ÃaWP0M<˥J>⊚:k,^bcG<; b-'lG͕Q{-E9r3<򐭻CJ˥[.k aqQL=n8.#(޸jrӍɮ0LS0LHΜ9+}Gu1cr_v Uhk -01ٴ\~s1fb|&8'gNBtΏUH\+P756//]*Wf5;LkV&f )` |xdA4@p1 k-s P,1ߢ}1֍9.@ܸ|BYsPP\'Μ9b&kh[%mR$ey:̔h8 \qĬaF A2ۣ?H¯RgP ࣜly璏}&Gm6degtP qC6W<;￰q(˭ܤOa )` @\?>}Z g @yuk24oeq8& q0cB((8Tvߐh=u3<62 یaZl8~k)a1[}ww~mrn*ojSZV@۱)`  P#yj7]V7 # vc((PCa~ø82n4**+ gϞ@qIe/_!MŅTTJ2hsNxzjRRKMn\S#&7k~uR%pdoP=ZwZ#k/1¦(v"lrm͗)` =KS9;O߱1|Mٴ}rL} N@s᷸&o}s{"io[.k1f+VH!ivEŋ7e4LS0$K`8$ɠ(]ZDf́bC8RQ6怠ܹ(.*G'WKQqT ]."}2>6;lPE/fᇙ qpOTxM7Hpc`g fegY=vm*YݵN g 8yMV0LS0LS Lfw/Ș  pE#ϝ=h0=<R9QT7vTi^&Ʒ1bB# utaBkd^"b?>؞Y/Q69ckmsA&F#( -mk44-!nmF%qWG?|"WiS7LS0Lo_xQ/QLiD;Gk{Aopk;da6q`8Xz?֧8%Z[ɇ?|/4[]ŭٛ)` F7X&o:M p 8õk硳V|?> `9`;ukgΝd.0} .ksŃd`bO07+.7_珬#,e(t|Bmi%m]GSG[dEgW(#f U^4m)` ))@o!rYMp9)$%%(t)k/aM ˥}# Y1Fc'A \>r15|m͛eYTVVJcc&v]ŋ7h7LS0o?$(FR;\GƺF7u\d|bp0 X"@2BXd朵Vrq!$@q&6/W⒲r1e{5NyPC:^x3۱\-SǴaYЧ!KzFxeK|ӺU)` )` ^9p9vd6Oa2%1G-DPQ:-EK|B` g5)E]T^~ɺݲ|y5Є})0P~[6WS0LS`+/Q|iP6f® U<+CA k.oC?f7$%((&<Gq"+ CBtL^Gލx s@-}tNvZߓ}j/Q?.7çxNDzŷ_#kk?LS0LSx/ а|[ߖTi9y߭_nFt(R|2kAq GoTȭk.NӡBhZ}(:raVWTjdyk+,&nHLWvd n)` 5#'~YObn`2YI ^ *A)?%"}0V }pQ__׌(W)>a4,_%:Pf /k6JM }& t<'\10'7w=2ú%ӆeI !!aK0-ܚ.(/+;auǶbbbl ()` )^8pߖa=QP*ue v U#PP8ց^} X)˓c1QT>yx޿-OX/ s7A1eR@1J7WnJq|[c>i}93j:ڐMZ*EP|SSW/-~&0&%suxFPKݟx/ )` )` A19wwq<ԧ0k9f`I[ _],Hsf5z8`8/ht:nfKU"ioEEG0P|=MS0Lk@NWw+ 2OVE;fX578f-'e}oِ6!*}3,вbCS!eyAKy(o'~0Fh޳kz։ Zq Ed;[S/(b0!9PU5bFߤbMht1㺮Rn(]a )` Ϭ`G}Go?yJhS<sA411#+B-\`sƌyk!uqScYZ;;sbȊP7om )` \eF~v.йH'LBh72ڠʆ~/e"K\> ReYQ<:2yEMRҎJ-Sm]{O#='2-;{8{ͨ9D૕3)"9 g77;MR,b(8+k]28 0@ی߶[)` )` \N=&/}3gQ<ZHkD0J\[\E &+/+e˚IyW"!ݦr7zs֗)H0PHq6mS0LS`)}Ū݃b@OϐX. Fp4Dfŗ2NVEh]F*| qy@u/fT pe4W˶cA#փOt.>JXpEԤ⫗3 px@n'`qn~4ttIaqLM AE)C½ƖRS@qvz7_[lm)` )HexxXKNÚ"DsätU\XTMF'_/$*s?Y Ɇ˓0bg` )` WEgQYZN&pEVB%ze x|tDٍ㫏ܠ(DMc#:w)aZFxZEh(^Fj4;s vIDky s6!jEe*hƼ Kʤq9qL3s@EŸI*06q O11jM})` )` WK^G?w(fBzZQnCpjM1> (@%LS0Lk_cg8vЖ0ցqtȉ(x,˄ԙ5дuy;oyXPLAP\Y, 2=6$G_Ba%eo~A૓yrza|BeMLbT(pvČ"w@}/sce{ew}x̺ڇ+dYHDO\*x߀Gzk:Y, X878y<Lb0pPϓ*p0:ivXƨkRTT9yobQL;7֛{)` )` ]߰)` )p(pyꛇ^g XAU` *ꢂ3`sM}'ueak_'Xc 9w9opQ P\ P\*19˚dN#=f/-5#գ״ =LPMsscd0"v @ղIJ\d2^$;X؅&m,\#l)` )` )(`gQښ)` V;{PVOaq2'F]$G00 bFf,:P|AA3g<@4iK26|^ѣ8ey] ,vĴ ufp@0l#h_E (a!x7KHcbz[j1A P(#ƺ_O)` )` )` ,j /_M0LSX< '8ZW=we.5zxF2W`e?8Ĝ#YFPa4+V344ŠXjVI!" fsZ8! | )AaZI80HٙAR:Fz=9{=#aĀV;L i[*FX<m)` )` ))` TAoȾX:x l hoqYp SA4PAtTcz?hðrj9}$ٝ3r]VvIi|C*Bc~U < 〰ތ4&AD`)pԗ3za v g%>Cŷ(~Wz +7LS0LS0LEE˲)` Y P LЛ7aWP 8 WrUD&tF0! :>vc Y1nNG "z Ry{u,[%#3pZs17>qLB`gRȇRX<-'/·}* oK1+%N?۠(:85OA0}qZR̝]d1}_>a)` )` )x0Pxw6sS0LS`Q)pv`H(޻&`ĺ`0\PG A ` Ϣ2F!,gQ0^ǁ5rqⳈ" ^/ťY=.VoYZųY:6f U ^q)=(2s|7;pL 8b1n~)` )` V@MS0LEaʮzO?@IDAT^]= 55;K_Z˕>@5rQMf(_YE'^ P5 3XI1ZxpxCd> DyUDvөqgRt*ó)(.L:L\ƍ CD;p\_a )` )` \ŋh7LS0F?K"`6 zv 3 nF}ύA~Y\;gq=<}&+|,pjǎ#|cP\(Sdž]ps ɗ$ S Q6 <3Apr|MJYu&VP\y0PWg-, QӒV-b'h4LS0LS0LK(`#S0LS| _bB.2 ,=R8 E/3΁gݧⸯNCm."lWwӈb[(/PHܴb b$1sS=9b`$m(‡vggdy2yH\u,j37فf"$V^ؖGƴ#+-)` )` )0P6{S0LS`(@PKyΦA`lcH-!m:Dv <(@XG28@X!sF?ZC®]Ci(쒣}e9"yE>22pZ*kebtXN3'ws¯4zc\[liؕ*hŌaVy&lXCA^\/Oq|鋿}S0LS0LSX (^ܿ?)` )hG^/ɫ׃aMq=썠g x@qMz1Z Q]_; Ms=(>r?VK;ݲlZ(m'H{snelaD >ْ%y6NG1m)x]&e5u:q* gTH;pfďpL77; P\#w}vߠM0LS0LS0V@S0LS $A4`b͌% Q^x=Ѝ@1'ucgy( ~yĠvvuKQg(mYVZ׬sgQIQI9ˤʤ2)R N$>]0:͛̑Y~ieM4gZK`nwrkN@fM1<'d,vmOm/~)` )` V@MS0LE"wˏ_+o hc0(LH砯B`A@fšMp(;?{:b, ұv3@i'ʉo0.׹ 1HA/|fy޾ew<^9D1Y$ct1/hiv356"j/k4ÏZYٵ^~n'l )` )` XŋgS7LS0c_oG?yS&-Bm8; /sV}Cqnj vi}"g bnڻ{d=(nI$;k?.Fp!npZF2"yПXOԥF O<;(d:39#yu`>@b?f<$=/ǘE<Q͡QiU^/| c7)` )` bU@bټMS0LEdJGH1 !rop,P7 7+(1t&rŇ C{5rŃK뎋ѽIVmIϞʥ 5|~$; $G1}i`>08"bL*G <=DvLfG8LIaUhdZS8{BN..BD2-*DΟsG8 Ds\G:D{>qH0LS05R2?$))RERX;;LSȮX)` )` \f&RS8A1#}4BW2qŵ{}xP07箳1b_xܸ}f--2:2 |@%jǺͲf<}B1:?M,.aåzs\0Mb|dH#'Fdɓcj++J*$c2އ^Ë6L7z>w쐜Ğar xN11ūwm5Pı)` 5sl}KYtcOˎn0c _ǿ|{uS0LSj*(V쁮ıopgAaWqf}ɾc(91=91NЖmqV{gp}R92<4+|Ct0#Wwp WaG p5bz1I 0>8 }VW(|q%1~]/(cBe%rȕ˕3R!𢐳ʙdN qGYMBD'>Qy-]B4LS0__ʳb_y|wQV}Qv؋t˫f , (YԳ)` )gDp"TE{@A\k  gB^@aWu}C]}1B[v5YZ]-Gʰ0-ܹVxdH0#z qSjx_x"q!#SI'$tV; ^jD)PfIʵ3<1aOAeĘ4W7]ș#oo,ẓ[O||GdLy0LSx_HOSɶ-]R=#W_<Rw]]3ޝۤobwe>~y2Ly (')` P EP?֫07V\@` p`ŮXh3 `8#k=(ŹUr[L },_^0Tj@.?vF Rpй~'ĸUvb\FqFRVT<[ +RԀBMhc庖Y|[1;\2jG>z|@N)` @i/)g)ys!qUut齲rw9)` \m _mmOK]rD% ˼o1 X`p\Dɉ#n8׉6a 5 o3#HAa?uT2FD z k]9 &:{JVmUu&t5`$1I-hh7}쀲jEǯWAGoU~(+S0LSP %+O?~Ayh˥cH=Lv#OߣC/;n ?ψ/f=K?![ȋ{wIN3*6@GK;~,<ƻoûc{zAHhqSr ^ݲ=7O$n_ieC"ݛ{-^[?- ~{2z4I06)` )p}+03;'{"?q(Xn mvj;Xtg8<v80pSh@q>, Աk֫GqCk"7?9䀴^~΂!b͗\?qT`yunM7+4f%A1-!7L;64 U=i#1 Ocz3 qrZM@O&cBsRUרʳ@f¼IԈwȠ&գ/ru|rz"+G*Qwztq0LSx5Vާer`İ3Ћzϻ{Nv=(Gpw`0?g;]oߋOHgσ4 FtN߷K!7 |{f|v&YʓۻE0SHwG~?ީ4ԋ{$ dvsnFӎ~Lw46p)#~wx?0P~oc)` בſ{ P߁6( 9Q9 }]}2뢑ObrTasr\0 !̈bpb9s#Z7h.i\RLg1@//xv#fM!8/?mߘQ0`:ηĜc} @paVi{F)3]n'3:WcDqǚ./D8vc )` \uW`'Q$;)mrI0$O s$>'ئ1TMo@pbHn ާA`xE]{vl[P'V'^Mqg^65/;gd>̑;^Pzf%"(Y;UV@U܆3LS0WW,{^yCe |Tgg BuרV@ap&C-|[-e۰?!‚]iG(NSnLS0LC(H1LܸlS;_xTm$>`6n\~TN4e=FN⻟B1e]wWN$NB(G2G vN?vݗcxdo,ym[R"iwUS@U2LS0LibQZ&== ~78vs!P̨Y-X} K0`9ō3ݺ> ˉoumO*_Dr5j)wU?2NU뤢N>vx,01i8!Ц1uclf8&iѥGYh;qd>x${BlG(r f=M +3LS0 >v{Pis<|vo۝+=488އջ#Wo GqYc/ȾGcAE' ‚C)y`>xɝ. />#[ET#y~~6ٲ[vDx_0Pn)` ק;~' tPz@9j{ӡp?u be{|[@0;;-gOg$IGkL"2blFpFԎD-5H D&e"fԮchzKr$@| ˪JqY&|w}7؋q'{2/ŕ[>Y~Ӭ'4v6LS0'=_}}g舄m:|_>Z ; (F>C]o88Ih4gV9^$%sS6VdrLQCqqSGehq=<GU ()` )p'AAm2fdviwAP 3&!3{!uʒ#05bPKP\X(sN׌Ό6?{Fn)m#kI_ZH<|k\2;ZHrB$NM@&|ABt2su}x|Ú<$xLG2&ƒA.0nl[G`;q^;ۉ$& Ww7n(څ)` )`  İ/ ZOp`d{'N%5$}gϋk$ aSg'ia񕽃Mِyx<Л GqlcŀwHc.U*je'_+㔒^ٷgg9v>SМ_X9OΠXw>⃯HR@1X%7lMkۍ)` )`  S\-7"dCwf&"qC{[?|<4_,$? ?#/v_#w-,6^wxx d(hX져(L~XvnoONAdh`VGQ͗(N{R7NyE2oA(N6)` )p*wbh_vҡ0qm"e/9y0<⫇ȵߑAإ dV3ҺjsNZFT-ߑk֫wqΒ>s,[ѭ &Gɣ C\^Y#COa$kEB$&gNO ݴZL`\D3.kiHѩp0C_x`7!х(&(޴A~ߚDΦ)` @nDzv~V6!V,5 Sj*`jmc)` ׹領vqԮ25w(<{8a-scz Q?-)-*.l0"'ucfDi0-&UU5(ut G`g11xPy@6.1$<{J脝 SjHgF*>vHp^AL~ J*1 Y&8P|$<+=FNټqYOd0LSxس~/<ûe@p|# 86.ϼJFMSZWgF"|S6G@~yH /Olj P ?x{'/I0q4muax% w' wRĶ{ IFw'J (r`)` c(޷Вw>vgl-gi7cbpNI 1ぱzP̈bGR8&WpAKi?G0I;\Q]'->wԷ-}SiX.H_&c?51y!Ys-+]ُN!.nhfC?+o}CPLd<⻪zת.ٰN~d 6LS0L#_}h|9 !ӸID#=UFEae3v,~6#'l,_Z`dT_w@Q˧lڴg`I+A{i=q?'?~XE8n~s~"sot|Jf&ڷK#O$;ߒfG?ҞlCDY-ݿx^V` \y _ymS0LS <_h=t ]^WᭃI,N6P@qhb}qż&i(..)pq"9\Q K΍ѵQ1zVV3\yRf9 #"87/6S\N1!QGFcb̎8OBx3 \_B<&@fr[&@\Md^H{5Fꔞk )c7)` @j_zDGUtuu8Z(P_ IjfKiSWPMB"7N J I>TjR ZWv|'}O?wHҵiSvhc-MZ?t0LS0ޫ(@Uᮏ-g1%4.BE'G <vtȬ@8 Q3搗P\$t2[lI疯^'+7 S)\(bBۢ$m/ks`4ksL _9ฤJRLL p^Ĵ>\se1=y.gcF6x,>ɛ/ƌV&)׮#7LS0LS0LkP/Ŧd )` |Pc=7c5Xkp\}ǽq))$ˠd"gSJMm*c*R;Tmd $CbSNbC'qL)ږ ],% EƵyOs޾$%dQ>wN3>F\¥pFv#Q[Q܀9'i vO^{iN^DSJ=0F UoT|V fvvl79qֽ̎O* n.N^/`EHꋮ^]}mB3;P\:wƽnz\{oPحw8(+λAEC3;<8 " " " " eJ@Lo-" " HX¹✠-%q}L>0#*eX"sٯF渡,fCމbԤNޓ&wcwTNAΠby5G51+;ex V'Sw{-uyιLcV M]fx FC(tCfWMaLێ 7KWu.ٍbʊʂK ۑs^s죏" " " " " H@," " eJ šͫ 썅o,tm)o!CW.'sB9+yrB9(nD$o&vvuz5eE<ు\K[ fS"aq$*)SC)PP{by]QQ|e IqKCSmݹvkgql U SRgRW|b]VW#?l^bV*)nlV sܥWh k[A1s@(.;y@XSS*^VR5v·`[uᱥpu|sx~?,F7?cDcN/,PRb;vtY¬f:Z[Bԡ*yؘk[tf粄)~3e39J߶-ns(ftÔČp93S{'qkj,cڶw9 bferDzg_8sIz'kC<*hQEqO.EO {y߾=G+Q\xYD@D@D@D@ʑDq95YD@D@ʔSod,,'qY p]H0$+sQT#<9y":\Jap(njeT. o E;v]VRIU;~I\D,2b%Zڻ0f|vLnhjq¬TVز.=d/?S.cnڂ1:uFy=0arܟ0Ņޅ}{R]+D@D@D@D@D@ʏDq3XD@D@ʖ@qEq(vsr"PG7'#QQ1egoh?n %rP |RU3p] Q K]78}oZ7|`6CD-7u(x\֮N2b<_cs+r0?rMI1Der2^<. #S."ļֶ-|lfy {O[uU})c=FaTV(f3{!k@(.{@`E˨(-%h} L9 [n+.Mp/-X'_xv c~⦦&Ke-. ںq}],"p l@uːmHZJ&dVw5׼1"p 82Wl5@:jӣo;2XQzK艝w}w߅WYE@D@D@D@D@ʐDq4MYD@D@ʕDq( +)jDqNpp.ơ0D'%(85D1*Cߛ W OnVR SJeDk*xjDH\Aeu!ӘȌmrBrh^5LrtKkF͊W ;{oDq%S}VힻQ,Qs% Q\N3#g#"rGE8_ 2 b_Ertp>[(*XHXQJRK5u@{m ۵"ai!c󈇘BDzm32k\5//?c[qʩ]e2+~Q̆tENVVV}&'ڧ_B0b]VDTP_8MNs7{\OyM{ܼ\b¥cܹ.ۋ#fv|YD@D@D@D@ʑDq95YD@D@ʔ?fvoEr8j"`%1n8|QI1(.?^x`"Kp~VeL|v(cyŐ][P<*xgQL)\hmwU3Iװn[3e'+NRW࿰ 912r90F *gQ-̸ Ό1YXGfk+޲c~ݷ=d)!jj\q6\ӝ=w{vǿϿl}(Key4i(O_='oĥ<  ZHXMJþ o ))v}y\,__Fqs{u8ىb&W QQ\P2w~r8d @Ɩ6Wb&._[e3E / ǸÉ]gtE3gWV]l#$M/~[ ϳ˗l]\9Vm]v73ᅪ{ܱ, 6.w={z(ce|4u(7#hF޷syt tٌ-6WURC| pZ y'䪆gwN5bTS3;xpޱ^'{+ ^Y)L 7Vg1NCKvu NچaVd y?^@:6ǣe.zìZl\ǹQJS&y(kײlv9;66Ո`s$ݨ-h{'{[VD@D@D@D@D\ HӼE@D@D d4$ѯ=c_tq5pXQ\\/K`İ@Hp10jXPc?,R)'KݎJ4ki;h6oe훶@)w^ T΢)ػYF>P.-.x f3wRN^<*Hnsxӿ185yzkԦff],mPKXQ능9) x)tr8XTxzYΥ6kDw٥g^]"_6lanלfYôae5 &ѱP6sg(4Xxuu;tN;cN:_8;1z‰bsKNt M~`qǖmN 0t'b%PY1؍%fqP]<༬"v$O1)Y܀Jh"suQ\e~hg~2k )b_XWfFO.ED@D@D@D@6.{u" " "pLMמ9a'K"YVBr{ {sB8UBR28Z}~k0F0.,v?W}DkPE쯋EqNƺ#h@$D@}Z;7C궻Y^\ .ylRwy,u\^0+cIDAT|UQΑ *҈ȉ6G N44ږ=|eof~U3}V-w3־ {1 IN;!\gym;91)|VdZ55ֆ\rs㼸 f#hf +Y9LQ}LQ*|Pgmkܐr7m!`C E_O*U(>ܸ$N@fv~6£v6cf1,8v%#())mg6̱Z;+}U#SL߱Ym'ܶFw. wuXaݛ:{8o>'olscgً?ѹJPqADE u{W%98\]HX*Y\*4쥴e+9+)(e1+y Er%&=lH4f3;}иD.1+K"-GAI d9Q +0Ŭlf񎽟rɓ8 Q`ι6_YD@D@D@D@D@ʓDqy7ZD@D@6_|蜓oN27'Z,C!(Fsñ.njn]Qȶ"̻o,a6c/1̎.)]c U#Đɋ-T^D։Η$IE`/A3ߘy Q|σT7'/_@@Y3gq¹4[Wk?*ܬ" " " " " eH@ o," " (+Eo~^~M#]%Kb%0)rruu5-E06\q~X@0+Y&t3S.gsgY4@މcUHÅb b6gVsތpňH@A "&8wj^ SLB`";ܱy{MM^K? *)QWQ&(.y۵RD@D@D@D@ʑDq95YD@D@60E;}k>8-;(>US*gd`D98W;Au?\bQu vDZR8P:[ Aۈ gǜxe5 /#()jT/.mbXn >55u.z1l@ȕA3 (16#&QeH Nzn&z;T޾^w4D$1E1s n4]Gs}Ϗng2$ Q\7MS0Y/ONaTݺ`) R8ض*Hb&Q\k*D3!) n4&rUWK[SQs[ *xp~^Pf#:.+.qgSB̌Q禖w҈efs#R7OSի@(.㛧H!z⭑@8dWa`_ ;yTcCTyb'=Y\,[y@<ʒMM^r2bìyЌ"q hHspn+`:ja^L.+(Eh6knN6 0f3JH^JET2[wqØF<QMX]#s?/}Z+" " " " "PV$vi" " " B~tokçK#I`'iLAK kɬrC)NF4(nZT貊wu {yަ67"$1,eo=3K k !{1'΃ǵٱ* Y̱3nX\+H1w`NX QqΙTymn~@Òav/E.%lىbVr5q E1>SR6"M*XqQ{g6p*  nætՈ`oCS3'S bM0B"pm]I4XlcnKޥsg{]Bzޝy `NtT4udž {f~Vk9ܬW2& Q\7OS:Y{SSP3Z a} Zwn? Ytƚ#.(%wgU/TYD@D@D@D@D@ʖDq:M\D@D@Dzdpe ߷0 EgbMؽl ê₊b4(nhA.}ZV d&/ڕKN 1({\W eЌnVP=\NSc\wj̎c|uژ^iQ算 tm*)gg]5sCKof ^P@)NA^}" " " " " M@f/" " "pQU {ؕ+jEqNs@Ѽ1] Mh@~K.o8iKL)=bu: j c?.&eFq^^V'WbBuMQ^12JiJd5{0笓ܭb*gm9,U_#"3cߔt(/u4[D`?owGyWapNGR8ƮD{'QQܾ "s݊⥅M_&kokC5$ m=v}BٝێDmwKuA" " " t~+vϿl?>e5uM0yk-);;z&/ *|Y6=7kW"_d#ځìХ]yk]2a/3 x^ us[8?pN = Uw5hTZmmӟn}աzX Q1RD@D@D͠TמBwB!!`h)DO\ta0{PeT/#?ĵuN.6V s{MmUk8{Z_/K(پvn>`C.B1ՐǬ,EvC@xk] y;6=7O Z]Kk"I;OmrUBR*p(epU*5].]1EuP Jas؉``n*K5r[k+*}^fnDpG{mƵ" " " " " DѺLGCvaK-@B沄3Q~5K]~{S}z% Qqョ\D@D@D8K^ΚU77;3_n.Ĺ:bJ _`4cTc#v g+U!C\7FD@D@D@D +/^3ܾ A;VBrt`X-L5$jJ'~3;`ùD?h\C@P1" " " "P@`qiɒ){o b"jjm~:exŒpXJT@6A 3[v[w=܈]V4"" " " " " 7D&D@D@D@64%oGve;O'Y \]UmMMyf۷wk4͛7!g.ٹ64K]/D/$" " "<՟Ȼck/YvaZQ!޽>gw/>K oC*" " " " [hB" " "  ?:k~߰_쳟yИ;ED@D@D@D@D@n%ŷ\D@D@D@D@D@D@D@D@D@D&( uJHJwCs@@&@)E@D@D@D@D@D@D@D@D@DV" Q|+ ED@D@D@D@D@D@D@D@D@n][Dt74 $otRD@D@D@D@D@D@D@D@D@n%ŷ\D@D@D@D@D@D@D@D@D@D&( uJHJwCs@@&@)E@D@D@D@D@D@D@D@D@DV" Q|+ ED@D@D@D@D@D@D@D@D@n][Dt74 $otRD@D@D@D@D@D@D@D@D@n%ŷ\D@D@D@D@D@D@D@D@D@D&( uJHJwCsRTRv/czّG~ak+ܦϿX'G2O{Ğ|a[_ۏa퉧{Yg}W|1{]uI>m?=pUE0qI{􉗂yĞxO>?SȗNZ`:VC͌>c>y| S[D@D@D@D@6*zu" "pH/?hg'ƞ*:vvyo=SOX6%O=jѧyFO+T'N>n[cYG"?ɻӘg}i;y:e/Ƣ{?2_] =hnO|bj֋af@~՛}޳=oO6ǜ>99텴>Kmby:"&/{lZ {K_wk;51;:G>D=%ƍ[Q'kߟxNgCgmQ{^ϯ@@@" " O[.Vob?fg1@1X~CI{|m?oԩ'Q%;xVc-q&upвO<}{9e0WŌ-{ݿXmݶH}v@H223qF'RXm]cuhfNqU{Jz3Ә'.:ѽs,)i{b~mxk^ā? `O4'J(v_0 .oj6l 9-9. w1p~;,FK"Xc|}YT{@q.x~3xo<)} 651j'Ytߟp'^z¶~ߺGgPʆ5DzŞp5涎(:pN2ٟYaUQF',L p5.gͿ'Ju" " " " "#"" "  cY7Jᵯ*==ߟ/oxp4N8{;O{ ;42=;?sASNdyg {,[ ;o_XF d`ޣ%j̒ zcļsO< ;=o`}&P<ߍّlS #ov7bdww 9Ȟ<q?;]qʟ3u8zlp8,Cz{b,v3X;=G -kNG,[ޗݍO_c߁CܳQ(ŭ rx.x. }aϖ]_o_y|r&eCb3Nf~_.8EqoڐOǽO{`(y/_.KBf]߭ijD$#z#" "P<1 CG'#u/hQ7d Ӣbi Em0 > DjVH'wȉxdxU ̉C١a\sxtOIopz Ƣ q@Z \p;<޾(fB4_(~%sEE(K=fi fUDZdU/zkC?Cy/{|Ĉ7$*`-{n T=McCb q;@:cb) v$;|ܟxNW##.LUC~s~0<\3BZD@D@D@D@D (S " "p[D)ƋqN@/ZCabB9XϽcY xQD,t|qt/W 'WU,| [B} Xzm(E G_/cURrϻ@cL;lW"*3-!87cE՟GHCٱx(Λ[Ag?ǣ7qJ'í^=| |4P8Qb"8͉~1{d(r`ǯ./|g$~ G5xU>9xO< Nf,K/bg%㿫Q~ArJTnjJnV@Dې@2;lCr՜{Պ%c"'%l]_`W̓g8 ;C e^ﱨ81˸8OB5*?'o,O .4)^WX:|I'K*p$w cfXEuy 8Q#=#{t? C(WK1Wxkw޳>[c U#G>:Ɠ·^UqE|P\KXX#e-4(KxN/hD/\˒wNbg.*.dw" " " " "PL@ֈfcdzGNXMٛkVKnw.s{KIŠc[?!9Y0;ʓW/)5v,sC [tUWxZ#Nn+y{^{狸#U$Gcyع{oחs/] o/W|΅b.uQhXvpYdgϓ bKrbhH:w=Q",W=s}=kŬS~A%r30ﵻ/, МB_o vG(" Q\D+D@D@ʕ@$ֽdvX,@_y׾[KܲA+Oy٣PGQz玶EUxq=KKn]jxZ ?~{/V tĥÅ`x[{.goEeԻUjh`TdAT;0M:?tv|l8{bh`.qzT]nch ~;}8/~A_ļϛZ+/{*xDw]oD@D@D@D@68 E@D!0gFUk_0!+J(dNg!OhXvxXq 3wt\J" 2b Pgz qbUt5~v` ν + Y>U٣GJ`Eah6;|?ORb:y<__=*>W/a(+U-'EQoIe^<}s24nW,c}X}V~ x>nȓwa8mc,3~Mҳ( (9I{?s,-sz\yՐW͏?3{߅sUD@D@D@D@D`mk2#WQȎ$UǢ*ZO! QQW|R&9w^TP?{"Dvd0qns'yu`v8R44!)!BT꜌=p|Q|ޑps{WW~Ț8"#^\[s8w|ޅͫ FuSs{ΑcXoLÞDM;o~ȱ|jƄkR_ydh*o8Q"ן 3CcǟcUg_s?kݟ`챼c'܈s7?dA/_(%~HpSD@D@D@D@$ vƇg PR׾쑁9*2(O̫)+{{yys@%!,C K5h%k>Q-cs}~rݽ_tnfx#T,K0?Ĝ˱/)K5_ @:̤^sw;'sK\dU鱂9ߡc$'8T ;Ţ`؈x~DCLui7g</ߙJwlD<Ѣ_(@3ˏg$ Pavel Odintsov 1 D$@IDATx`E! ̈́(Ez)4 EMHW"@@+ UBʵo%p3of۷odqqqZB D"@ D"@ D #f"@ D"@ D"@ 7D"@ D"@ D";Ou'5"@ D"@ D"@M D"@ D"@ D)dSI!D"@ D"@ D! D$M@Bl8{A6"@ D"@ D"dqqqZGJ/_E*JG|:!tܹ=C߯oP ;*$f@aDʠg=xN.kSVV0ױL&ؔkpo!i}D|7 ͯVߡE9 yaobJhp%By {W'a^D"@ D"@ D8 D`GŠdL6K ߚX1=CEanCT[Ɣ@H0X "jb]Ub(#qzЦNh~ ,cCcİ#:-yz?߮!BQ}`];ޜ24l,1ڍQD"@ D"@ Db|1C@LRiO .9eSH͹Uul4@DDA.V QZqc* Y#P1] УUwe& 6`-j ʹ4񽐴z25\ =x(3 '-]c7($u|hFD"@ D"@ D aNCʐZρ:wayca_tRF6؁ecEHSTvK` 3eևyoTiV>" -3Aހ_rۡe\үiUNL >N!9Sxj$w*g`m!D"@ D"@ 9O)oK5xoT}O]0Գ;Ġ&ٶ6 Dg peF'g~oreBѝ'7;gl~ edM$7lMD"@ D"@ D mN+dkg׭ .*{q랮;DҥX,&% ُ X퀳*HO&1wv9%tcM{ųtѳ]ĮpJb(YnK͢=hP(Kf{ߙYx>>*6C?RqxG~UP`G|NC:)\W9oӺV}?ʕ{kĦ,ʹI/3C0z6T% D"@ D"@&Z)/v Q6*+ۘb \}#$SGϡ*&e!_0hU)M UH3arkrk->ݍN̸8jDQqxs"3H:Xhq,L:Opٱ;*ILb4k-5~~;TCId1E]5 #2 WT?_ @5.&1;ygl7'll}ƃh]LL ϨaC#Vked2 а\S]k"f領XfAXa,RNGS6Ū`_9{DN"@ D"@ D ;w/ѲmtQ %caU=ntĺO+Fxl~J1-*,d &8 Tqp@H}k/n̪E+|"VV A!q4DaWd {sC^$A{LOH#km /&,4K2[,X]"Jr/u`T0K/`;jȢA>.m>FӸ:?}Oiqp78h36%u_^kz|qU-'2U Vx "@ D"@ D";"F1|Ì| a.Y~Wd#(A*Tyk*0zg8y4Q82%"P$<tjhbQr<ʈM0<A[YttjΆw_qx3@zMn5*b/=f4%*2蟧q#_,T/g]zo,ozL?b /$F+$`>Ԋ%U䌽J-oK3EG&n}%+Dr@dA8M· 'ah 著&>+yhYswÜ6n Ko||mo1/W%lCb}&{-`̪5U?9uzct5!R$V D"@ D"@a.ZGj2\!֝xt)/4Db&1SNs״CҴv`2h-E]ٷfb&R_G E_p1Ձa&7'o)]WD8q,P/,uG$8] _<2kc@ |9^xe\Z.i(xc>7?6. 6^9F ==b_g%пD"@ D"@ D2|bѝ -ODȾu17r^caĥ'5rjw`Bgi}FD\$"Iv 8ovĠҾ9’mME' e̟x 'ump/VMs㹗>RBi=ѯp(ٺ@*+lsB%*ȋ)g-ϔµ25z_xo 桯Hh]k>)kK1]/4gw~`j Yqr[y]`/O)ZD"@ D"@ $`m5tX{&N0):CuL=; %̝r8݅u8 G^yWNů?Dߍչk+N+B͙@SQ4_.{&ɃVg,K+.G uZ{]vwۥvCzCtv5)>zУ ԮNp!}v\$~TT*o.Vn<&]{ ]@Ckn2ӾWG Lw{P|~])S]b<H$K @kK!t YX`ꡠɦB D"@ D"@ro%,γ"|}Nq3ֶ =\ƟjytGxIkPF C?ĉEex5E- ~`,n˻d‡6StLٶ2J" T!*偰O &`ZXxps+:~T>]6K0"@ D"@ D"kT 9j(^!+טxt `|\;~C u~->zǛk~I)Phyx>~{GLi \'6ݫ%Qa {bi]3[Ş|>b-k܄2P~&59q)h r/'KZ|j*ۖ%K .4-wwöklF= ~:FkwnY/ui\qxL~! p y}({Q6T"@ D"@ DOwIv_S%"lʦп-s'OlmHƪC͢׷**&',ǕKzc#4ðeѨmLvY'IœYn&QRKߺ"vŞ푟I/aPhR? :Qg5K_Z~fRh?>~Yg(|p D"@ D"@ s [\Ox㠶d%ą2X?#3$qjbJRAzYC<FX'NcV#/I<xCvqa=~;pp#d;W4h]>̌l |\AjCjV5᳕iA9= [}>ؒcwp`tc7L!㗯^2'}IevTS9Z!D"@ D"@ 9M7}8zsV5C57 q [~3=2 gܟ)}lJS6Jgu>on U:V⌒̂V\ *BV1 R>ok /ٟuvEwDڥmɵLɔ֯(J:84U,+d =0 )Hα*ipۗP.Oore1  Jf&uu tp-6jPEOٟ>^׌CG;zT H* D"@ D"@@^#dlWe8p:V0Tѿno6 قx^h\.ID$Kʺq>90 I\NeU!1̇xjUetm.˶a)pC>K\dp[ o `swڥuL˰b?Yyu ,{E/έԷq|XK-ms沫M?VM:♯?ALCUU&v.=Y*@{Kq2=,YvsNp/n{mVW6!W^"@ D"@ DZ8}GP5MbLW"w4 -uݡg]ĕ+P-Ph8>#;nOTHb}Ѭz j!S>kWqdBtln'ߴ*am0O'k·pV_KS>j`/!sҭ691&fig 06`!I"ysgLO`ۂ X94v92iX7"@_ntsˮ!-1'&-]} TxJ )eM&D"@ D"@ Dp2ԉ4* D"@ D"@ DH@ @$D"@ D"@ D"??}A"@ D"@ D"@-DA D"@ D"@ C   D"@ D"@ DH@ @$D"@ D"@ D"??}A"@ D"@ D"@-DA D"@ D"@ C   D"@ D"@ DH@ @$D"@ D"@ D"??}A"@ D"@ D"@-DA D"@ D"@ C@?&D"@ Dj2TZ-pҥ\ԭWHy}>}ڴV D-mU ȌI+!2|K3WǔUcB}GĬA ^UG|^KƔAvoFbծޚ1]8 >\/=Uz2`<71E("F}ʁ@dz+ɫKķɝ >- 2޺︫^w{m4ՍwG|xha_#ZH)e8ޮY\gtw|3>_GD@S ̞25d2!?Y0;SZjxhP WiLWL#X\gĂ)tpd@0}fwmn#Z~ޭk!h0/2JUl SCf#GO^ݡ={Fy7Οį?忻+,GJ &CD?tV?j#l'ݿ4s]6UL4X:gOesVx3pQ7,*od_X{m.ə )e%G`mU&2cu2,ZμІ3xvy1 .My Zy>vةUú#A7v2boLM_#^x/|:7ϧIouP-(Ǐh'٤ ߢUNJPTa%+.nVVF1;*Fvir((f4QSVD[tݒiJ!{E)j ^(B ѓu}?L|W"/Ԡ@JPVwD|, 9޳HG;ݿ\7F6 [>cؐ E "8h͔)\,LTNhh(ADtiT|= WOBBNvmK*e_&o laI J Jȫ}VqX4qlر7MCvf+bVFow/] zw`qUinh ||x Ja&"8>fq_cr'3q&n>|L^#-vh G셕5{t /ӑՠ(Z =}i6_<ڮ1ؑi,M}hL%͟L+OTPgAe\N: ن}AyBIiܫbjuZʄkkPTZ'yVnK/6WԱx&"VrU<5i2f1Gy|?L`y E' Oظ,KaH5Ux=_}j7~)>긏F L?ytǬTg7GH%-oѶTrT' pf&zxgK$DkRDAeM~yhMD9pv2/EeZDw&-ߊM >ft*iHKCʐZ*mV|h57SbѮc4(4!/KAGLh"@G_]*7Vٻ̌2uY2CgPLE `NVosĝ gUc^-YZ}h"M1}EzYSCMqmteFog庐4v^*zHM]vkDv, UxmyS8Bcc2~?Ӈ_Rl>#;[]5/?V~ǡNDJH pcy2viW':M̶i"@2 WL}߿ތޮ~Ef"Mku 3cA%庍`- ED O˯]R.8vB1)Ck 4.y\8BԵ;Dҥt9l !_8Yu8UǶTs;Y{SuH=m͞ *p¸`/N+dixҳ]Į`o,nz.)hLT^^@16Aͣ[anJXmuY>˶G)R[ן@@w]_ɲW/7 ) '/,/k.g4Aiw6-;!!*2e:T d<|7C@LdAgôY7ڝ8gI%g$,bȐs/l-IpZq6#ǿ-=[+> 1E(͸֯BR_ }ڕJu̿- ֖GUbfdS]^y '8\О~y*>rG/1*iTJGnؿ!5bf>%2Xѹs("IYH2ڥ`w!cËػjFe[`}}jʆ& /~S^Qu𑝘϶^FFyܯpFfѹGxKy>\9{XT 8+˃|UGvs<,I:Q"fcwu3͖[m;? eв8i-;pd 9>/TPe?.~ Sm|,펨{#ki`6oVXL6r>Vi(SPx4ILƶf`]Cse-mB-H~v*d>N{aA1ӹ4W2+ !MߺmZyQT8{b0+qK} ZJGʳ3gE[0iyJ*"|P׃Cޠ"@sڬ^X0xYYg|j_gn4,U[/tVh]cӠ'E" }kPATh:_Lri/ѲCATpBP2a.>9k{CDt4[e7wS03^6'>I8LOS6pUJ&8&Up>\=2aߋڋC6߮ϯ?p!N1!rH0q(ga{ ڂa ^CVߦ߭9 3|t/_Ta(T %X]UuFҪ| w8wpp0 J'볰^\yCxC|_p!.:i6pc,o>,'lgXBGxKr4}ŭVsg|Efenڲ`wu'7}1{٘脱Pj>V$H.|.|vI53u%= AnmIqwgǫ {rO)Mf/0N Hu9lj1)&=3xk-@wgerfШ;/8Oz{Ptr'.ywgFs 3anG;÷V FVBi;GHB 9m d?3z*Ϳd*3͢q E_S8 Bcs}wW"Il&q{]8W+#f EB( TW_<}3Mik8i:^f{H؜TvI$|LTY_L dad?+=PLaֿo:/͙:`h-\~e6 {]mc24`ǛlYS|6 zҐ0gXӢ pI[h7m~MP_;_/7*:_oޮ*9{.gPXI.P獫B=&`v(!" KOWBQF䡓{z&yjuYttjPv}f6f)ql/"L4C.)2a|b|GD 2;Ο_VJ:M LZ?Ջ(K/o㯣\>ʗ0їdzCGep`󯸞_+)_"Xn{{P.1FtuqswRwmrGayjY 3CFonuS]8\LOuBlxw mf1^wje'̛L->8=BByO&ݿCiKu\i[+ E`i(6/CF7LfMk .cY֢u;!#+ÌǫʦУ oA+aLgkϾ5ŗo,uѦaR@4ܼ~Ǭ X t[N,8yq ۃ_9?Syw}iyoZsZu>[éNG*>FR'rrn<>4Ccl9`X~5\ TvģXzݶ?[׌dI[LZ(*{ޫ甙 d4]沌MMUrs47xVsUtίs맡Hf0g L Mb&j?Ʒ`} LJ G#ykܚ\%:03ג]k2%nm_LU>mxZǼ=>{Ϗg+Ȓgz,scjUX2>NeSra@#Y 0|f<)v2I.|.lvI9q:0dd枟6ϛы4zAt9]0*dci`j\ljtEe"x?e`"u6ߘr_u).tSyj3I5nq67RVz7qV9gб^^2-<0׮}ٚwϤ9fVe_7' -W? zfoE C4x5<.(%pɾ,2(nbp?aore YVX/ݱ0_M▆ߓrk_a-dg\X zrFowE$w PFa1Ĉs]#]%G^px7-!}ޫZk g/VIsg!< ׻Hjؕy}2jzqmd9 VXż2txʇ{X׎;dzL}iay x`X0lYݍגC-hh9I VLElghoa:\y |I3R-t}Z[ 8]E<zdFo)ҷRj]_:87^'oW?@yC[D؇N.[󢷈w6ϱ95qB$ř~sK ;]ۡoS˦y{@z K,ud#ӗ!.w}X.RC L|2ψ/Ic~r}6jW8lqnW >Rڜ3$J_,\ORoK'ԗˁsQ7'U=JCiqrڲgHKi]~\ +ek3z[-~K63>R=_ؿW3;_|%py$Vq/CbaSIvƜ]oM=>]L~^Ke!u_^忋 0UPn[:{+sTT*oRh|\3ף=fI`rL)#8]nӿ0fѤU5MZvw;&Oo6ᧆ24N,_q. b:[FG*>Mf[?fc"Vd;x> 5|x YM[;'V([W1A= Ro"%ۚ|~)6p^տD_;LG`uE֍hm7'[–z([{l.A[_'Ud0uW >wwf,dzTC)?ݿz=6Y7tVlhzoMq]\ %uoί"FoGzK9>R=_=B}7!jIHBAR[r8(S+~@/iwVLJU1÷XK^{0gx3&r?bfkPF C?ĉŪ$M$^e3`Xr&0U߃)܋&.?x=T9yW9Fs4YqKNބ'yMsJv^QQ_MIbHJw{TAd<(NpoIHITmfNhgP9ai(҇7qf c]<|_z1qfiK*}c 6JǩVjnp=+EáOD&d/ zM/3nnq+Qs]#R{-FpJ JA]lO|;C1R+r1?]|HEH$^/(?~"<εϟgWTmrRvKq r/ Ta6o8' {fXOu=G狜_~(H@IDATl!d$z^/)x&ƙ2-yj.M/sy\sT,_, 'fϝ63MFvMQ%Y8Yt#LZ5)ݒlƀKil*8U}K(r덛'̘:>qO &94u/%U'Y91}Óng)7GAǏmkqaV?UZQo b\_ƶV3;݂& V[lFo+^,SҰk\y27ɨ:&Vq췘p&IK*%~U/C Sd-"AS( ؋S"rw5L_U8,oCdYW*E#'\mgl)jt,8fD9x\7?/Wd\'G*} :)ϭ^=x+[_^dG(3qy\V_yeyj$[=K 1ow~!ܱx2;6A*uRe*z[~ k*G\ Di^E/qqqZQ:t_zCO\]w.F_)\ݣ?֢}[9rћk|&FT7d:_G.^6\s$j=.Mł?/4 i 6xQBT>;rqܵv'&o5bidWxߝTf6:q=tlRo6UH*H3OO {kNRܭ^9O/|yReL\:v^<ڗW#(6 8=9O%V/TT'R={?-8hO]TyxMMl""e(ȉ쬮w-qzeֆI97;QY2>,ڜ=Py3gOL n̜[G*>"!fv-):e&T=:W3svۇ<ls, h "TP7'5}a@><ſͥH^:*Y _7(cj" /;kږt%Th)Ir:OגKT۪Ws!mfl᣽ 闅qmbUaZld :Uql[ujcc5J W.?).ӓM1mF`$ԉgܲJ-SF66bmj8{ܿ ex*>RA"djnv K߮("Raޓsa&j! w^pIpMRHA c&{sג ̸{j,| |004|3n:;|8K>sL߷A*\߱ ~\Ľ6}Jυ,xMJJ3wRH%uUR-zw~AwK\{∏{ܞR\ e_x{㴮뜮NF/|ur|)b}ޛ'C¥]4t ۙ:OV T.yj0p϶}}^{VIkǣRA-N *ob.{>ȸ %-t@V ^KWquܸv%Pz]T .E˷lx]ۯ.XOA]{q !5e')ozofz)ൿ_1RxK, GTT83_ȕiP ,,Fb2Gw5q QϠ|s(%H=e mK|\qnUpLڲ71_RϢTq{stOPL=V1 -B`.Ӟ0 ۜe3x.نB8Ԩizқ8K (RL~jJM޸ru5)ƤiXp|8\W!pQD/zWA`S~+,X\9T|By)ъPY8y;qa![e"usؓ'gG\s,"gHVT߮FVmo75b}5v3H람#4-KG~U>v{=$) ORb>e%gT#C.oR?.(+΅jmfuByEh|Hþә"޻ryٺ]Oq)y~w ,W_RT>,gmgUc@Plӏ-]yhc÷6 1Q(*X7zIFC"Cܣ7E 2*{dΌ?[cS0fXVҧt~E~FQTXU*"b2@+DtxD6y24`ͧ#^@/Mvw#:/#J7]YIh}pa05llJ]u7Ey )ٟUnfU "ēẁF_Qh8GKӐ|8 ^^Hq^X*12i3|18"u(=Y3S#< ?Uꉯ{~nwPAcx*0~9@r(,?3_х<2JnRT>s=F*ff5${u ,{E\L}7E74a3R{\;+KH[b~}}Zwa)6NRY }#FÕ4A%۟AO)qt1Swd:pʮĬ$S3cUpf![<<_gd}cuxm&>|\Ʒ]?7V_>/KƙRPt,Ù|vw\K5]Z;YeVADwED$Y\\ۚvYq "T{2*$onIXhFBt0ܹVXR )tĿQj!q: dϓX`N*c8iy;#-o-kW@Lv A,zNc5N\C_4Zv?kWqdBsٿaPEL};|lfPV]W%#uRc6c";[Mt/w޸︫?q-Om9?z.F:OX{l?7ͥv:U_4S{\rqqa<%pRF#:,H>MU`ogoa2zװbtTJ3or!.&Zl ?Y,)[} e$LFtQE输{D'0z<,'-1ubFћ^3zkc!|1+m6o?H" D'Ͼ6VD!~.&z){Oکʼ;bO1y9tֈmD&]V﹵Monj(k`Z\y[cK΄em /\Gh6gX8@rf^^ݚ}'wiOr@G .h\"Cciq_0iy8Q_qhRmݔNYYZyD\H=v*NlD_ 4.ꖋB.I_F D1w> !⌂-`93~TǖC|tq-̻VfmTu,ly@ABL [eQ<3|Al&g57c) Uf73k`#^Ħ>'< o$"SENc0z5oރa$ ~Elq0exWB+AТ/q3Hxej6:E(/[}>sƽԥ^$}Ǐǫצ`V(%C05St?q?/jEY>5Fno;1pni DFxd1h؟{ G7=2'*@FipO{ak DTw;Ŏ4Y|1㢉EmX^[FBf`c_ypiK76ѨBJӰh7g5Ҏ@N\koׂ޴BQ ;i^_J ƊQ s1{9?:;]w^{]}UW9nllMmdTN6& (iL-Pe$m͇t)FID"@:,:̧ίǻGѳ O"8pv^z,ضjm^kꚇ7qx*6_ `ͮqS)|W&V^iSB)ji4K3n^Kr&V<[Ʈٳ/1r_/`룎3(Ŋ2AAe\lU?R~ }ƫW~63q,5 K؀_kw`%$0zpv&c!~{ed|9@kD"@jgҸ+J&X.hހ-82k ^U@˕m),>;_d]k&0}XC?KLA1ܩ0#֏0S)GD  j 62K+_RW6/U}_|ٱ~wcN?r'cxװubC^oT}QoEsT g!uK&N|.zO?b؞1|v(axYEϸߐ~ D)i\[[W7"UEFo4Z!{0?{E.B j*"% Ai Q O@E(E)E 4ґB Kmw{^nwg͛ξyo847:+߫k3bͧίa!7&bݲ.1z͖=>[4z l93| 1.fx./7%#{t+6B\@+_Zy) DdX{3ԧ9s)OSsFcfVw{Bvr\[#:ş;2÷"'Ii"@ l}"ޯ A哘;Rߡja75HL  [:meo%Rř ,>nąs'6~yɧUPԹ&:4q^6TXCH;E2Z9lȞ+7aiQ_g|\z6SY# h0Y?"ōfNjY)mד׉bMw2*6BFY[¢e?yr,eE6D~f~P%;c"_~ͼ`z!F݊a㪙U?hK+}sYVouQs)|O6HU~o˩+ojyo4<Zݯ@{ǯL ;=z^Mzтћ/rNfɓ ]Fjt 4J5N"@ vkf>̦.U i?|6کk;d Y[>BiJߋ_F9'7ktnZ) QL&}{DSv~3 ğG<}c'Gʩx(ŇS |fBP Eä5h0&<+gcmNE@0_ c rNot( rS)_Æ-1.1ݭ_w0B^h>}k[dG[=;z-3GB4 AGDX*¥!ZܮȥRM\Ook, N0]3f?MUԫCbg1ybJZɑGr!~׮)DT>F#Bx8o^nI!b2aֳ(5ۃ`sF[׫Əg(w}UٶjR]uhK+}\d9KAu/̾-۹ՃqS~ (l}Kz?ćgbL^xhtrX/W$sTUԞХDvqJvVpU=\d3pݫK>w;~2'w2,f6V2?e-osP X.F3⃫Eߔ)%D"@db֥߃XU' Pq20z3fGDx((Pq]dF25Sϔ!fca#@ zQQ}Xv4v[#.#k>,7nUy1O\h7g#v"~oŋu<W @7n<.-fv}M9mMTӧ_s'<|+zSș6.% E-#ra&_>f`BsAzHg,:j@~ց А7*KJ%#"$":!5˅ _Y:yhSVY> ϒ@!-|k ߼ވ!qֱI&aɹ#S>^NZJYYVFCp,ލ{߭`RxnKf&Y6ytktRD ``jw8bZkM bcǗwgQ>ЇM=&++}Tnv 3}dTvWydZ]íҶqht,| rQHaD*zӟX<~;k:'ɧD"@5l `_<҈B(!1ҜaF+C-7&kf(5 ["|/o0wkCy7 sDAmko;]7EKPr;w*3W3"#Q$2 "Bu_$A-[9KSwQb]d#A-kX?"M$o9ۘ[ilO/b7o$rU`k\ŵT>:ڎfpaS}N@_;ov\ugB!x{gQT,y sVȜAYFj[&qD^Oځs&bs_udS׮Y/2"Vfo*W CI[hr, j"n9s$uvݿs\l]5ڼ *U|Q.\owV}ʌʺ7yX9w`_(3ŰWL_z CET_RzT6ʗVW^=aBkXWtKϟf 5 Yy.ɈFP6^8m G*S#];dp\>\]o1Ur2Pڽ^4^rG6Qk=~߃w)fUKEnֹgݹqO[dcSӃEx ?QXRb7:XG婀N0)f}sj㧭{)8qzY+aAJԲvrxjy.giy߅4 ?ۢ]gFV_clo; ez/127z+DFL.Ҥ#L˝~3*~\ M=+栢&Gȃ^ ]d3\nƢuj46M,ciy˨ΦB/ʅ7|w/cWS1X>.an)d~6.&̠v=O Շv0ʗVx# N} pͺ]kǐOlŠ}PIhQNh=[7lGӲˮR}hy<;'PڙZ=x^5CBە:op檌_ ބhG̐羚 zvuF]x#Gs&H ɌZn$&:Sb8 ϛWr)˸G^O\EUxs>c_m*3ޤ1-[<# ]X[5u@kY9ݧߧˎ;$#mgw i XF,"3clhEڠma /aA: 6gFZӝ: E꿀|p;O8[UeFt)B̌cߠ3R @%OYG;x%v@IxfMw,*Zy!]-\8( 'ѥCZ+:QB %f~w>u'ʗVO k1Òw`e)먋w>q+C8=xdӢrZ SPpKhp]N?] /Jʙh9I;JC}ǜ E47QWp_7"aSZƯK䢲!6jb1>rjԟUHsENYCJ͂~GM0MdUՌK>\|5-ыIs'k xMm63?h9,_W'r6"bPUC!EFFd+y}n$ݑ}2Uӝ㺣:#gySS}Vݑ%~fPʧw{V;*&$.?Sŧoë163뾎\k VN 7[L;0'ok2XB3p-ٮ(Ma*SfG֫/@Y>j6ڙjzyr. ۫mcFo0&*c#2*l3s^_8dX֠fOE^~d1  D"ʇ|OT%yf-:Nء;efϞaAG|X!ם{}|i6Qn!<ţ+ϿjI9WI4`~يᅳIny'RUD\c?YGZ"*ksB7*3p=WL6Yc[c`1x}@7tlYZWd ؙ$Nt;vmF5F㒛!~Uz}0J-qU.%-Ƌ6WHU16Z/D\m:kn| {fSd"KvU~HibB)cov]rWL_Jp ~[Jd)ʩ$;9Tޥ<) yhGeE1'˗sV;ǟZӴlQsih7MXx>4%ol{ݬCg%`c)LeJ"@#O/o){Ul {R,Z}p#*)9F*QQ۴&5Qnc8e2 >Yy&܍{awC[_ks+r*$歜)B v1/1i~+О@GC^ِ\3ZCth6Ơ_F Y:u".mCv;/`,YiybwC%D"@T͍0(Gs-Σ!5ܼyߝ;Wߩo(kttyʁxaEQ;ط~f=ZQ(*;aTSTA44:xqԃB= 2lt˟5s lSwTa+>)=R19ܳy8Zr r01.ߩ])J<6ܿ}QIwFC2fs.䐍zrwYrW 9X.]YJBța|iڽɨHP-)Ϗr{{ܽ>;Qr]2aߛz5+sy4}qsyKٝU;:ydnжdF&yCg^_d Ĕ| :TG' D̜H'yZ_WDzaLnO>˿E.3C!ѢQ- Nߐ5äoix't [ILiY鬴WHA񀧙r7w (/)3/jܴ(CHT|"Y6 _HEyV lGk퟉p-ie4FLhrexcQZ ŭם"IK+,>Ty0ʗV-m]JC*J{CyV??nGeE{-J7/>uuO-,;ګ<ϦTLێ|<ӵPHbŎ.-~^c#kw. D" 'r:Ern(fc-tidulz̗}U3|k271Qk; EdY.U \@C yU/2 zS {Nd7wViPɩQt\(>飣Q$cj1hp h"(Ϧѝj+||ķ+Wy_86EJјBov/1{~}K9Kq&a<*gm ѥU..:eGfɍfl. ,;a8.#D"@ K]}4%/ oMŒS0&q}@2C0WжdcBί^..s>7T*^wMB9 ̷/A粲 [`/5/˅jGyO6c@}|ϳ]U#LgW{uLM^pUjBo^N&a*;Vs0RGFD"@V<kcdszf.n-P9t }?XU'ћ.Mu<;br!!6F`It4o59qæWSA])V}۽x녧۝1[k|iQѹ?گ/A\ LTmȈo=Q|&F{X^,W.~O6vQ 0K*y 49RӥpQ\?XJUhX?=z#Ǘ¥j̷'$.d~XeJ}h4IϑΔy'm/ʳ㍜*L}+`m7ୡ3|юXlo8/g;C };voV;^ՅŽࣴ<̞D=F:CdD;fgF.c4$(/ D" _;h{f-vo)vޏ#MU*TF6Q +M#fH&7igq9<{_CpTUFNЍ8bg b}?ХVf۶I&MʰoKI&#@C۹[QUY7P& p,ΦDTT3[; \ʃ|4(Zނ:i ><T4e)Gz}/]w8(ቢzBYۏ}p2rժvFY]VEMfe;~ GNDžkA(X1ԪUe>slݟɑf ecEzp9s-,K]Cq16[zaDxɑlZ6[ӂxy ĸDiJZUPӝssĀ:uN`Op!J7:h.8X竑XWta!bFIύLGo;O=/oA/3;ޔSyvh]/n:|%sk'HT;BZaT }l;>6KhGyOca_ٛ }w;{'ګ<_ s}tG6+`΢-l|Gnb~ٱ[}JFw[į\ǯ#G:hBG*(]cnerlR%D"JrzsHnb|-3hǑʾΰiHوҨT9v2e>Zy 1EY@Q#9PQh>xJr&[50Zt`ׅ~[c1y19WWdzkzr\(V= @h _6^vgv) b!@IDATW)Ӣ~ }O6BiA8"3I # |i?_=W,1B˩L<\Nwe7oDiШhձ,쑿 _Ֆ.6D=Y۷/2,xX:NZ}ۄ`lT9 ‰+w9K?Fx'^WC>lj찶upYRtWpp4z~08$`V.waF֘[6.8WzS0A+4$l; ; Zxo\\ªwN_e f~׏ݝʱ62/Ѹ,U}RsM5 刺z%9*(>cM|s0YE ^ {CxDdYρVRJvvyxߤDr,%M'B[GMQjFپJIsv SNU;鎜@,loy?،3w=̽?K(oѦsg]v*4_z=Lh *۶;`|=XˊA,QkFok(;uא 8:"ȣ_"@ :]LL4ް#cSMƅm:W="lie;:gn^~5 J Of4ƀwڢRP\/p| >=!羇?L d}Ϩ J.0uYPcyv`4^ChhBLv~->]gb;MPe. f踜 'wnWh:5E ?g!~Ir ࿁&G[V|wYTz_,8JaaHMW#1)I(t׸\(d{p`Kx:nb39 όI䡭Y|í+MJ́0*үߡ/ZՎA`2Fs8m{uQhXALn=C&uGq2eF]6NBQ0WL&ENڝ+Hڵ?m9"@L ߏݧ"@ˎ7hVrh<xDPyDn4e"@ Dxꄸ"@@`0k0VriC*jT D"@  2|?,wA D"@ D"@ d"P[6דi%Ǔ"@M"@ D"}_ .t D )i0xtru׃M+9$M"g3pJ"@ D-&D"@ D"@ D_ W'J2 D"@ D"@ Do70 D"@ D"@ D}Ad"@ D"@ D"@o)a"@ D"@ D"@2|*$D"@ D"@ D ÷SD"@ D"@ D" dUI D"@ D"@ ~#@o D"@ D"@ D $"@ D"@ D"@F ~CO "@ D"@ D"@/TI& D"@ D"@ D&D"@ D"@ D_ ÷/L"@ D"@ D"@2| =%L D"@ D"@  @o_P%D"@ D"@ D"7dzJ"@ D"@ D"@|A ߾J2 D"@ D"@ Do70 D"@ D"@ D}Ad"@ D"@ D"@o)a"@ D"@ D"@2|*$D"@ D"@ D ÷SD"@ D"@ D" dUI D"@ D"@ ~#@o D"@ D"@ D $"@ D"@ D"@F ~CO "@ D"@ D"@/TI& D"@ D"@ D&D"@ D"@ D_ ÷/L"@ D"@ D"@2| =%L D"@ D"@  @o_P%D"@ D"@ D"7dzJ"@ D"@ D"@|A ߾J2 D"@ D"@ Do70 D"@ D"@ D}Ad"@ D"@ D"@o)a"@ D"@ D"@2|*$D"@ D"@ D ÷SD"@ D"@ D" dUI D"@ D"@ ~#@o D"@ D"@ D $"@ D"@ D"@F ~CO "@ D"@ D"@/TI& D"@ D"@ D&D"@ D"@ D_ ÷/L"@ D"@ D"@R"PaC%&tnŧH!s%4lגd$%%yI/sr"@ Dv8#D"@<'@oJ'MԈHgВqvI@?FP`(U  2U9f"`]vcLmwpf,DZu*ʢpV.]cbM?Y֨B~Ң2 4.ï$9b b+!$]mx4@I/XK\nZwJ;甕^# |oXN,H c"@ D[1ihTK^Ѣ, :ڌplV w>QkҺ/Tz40@u&5CϡkȈWBFaw+?eF2bcg/8?w`V I # n toZz4 zv;nW8ToEJIjd0sq/((;ݯg銑/^qu$6|<;.uE(2$ھ F:f]= k[z(e<=xS=)H*(UȢHUT2qDA A>"0 V`{ś/95j>AywȐ'@o"@peM~9 Ah .#"C.S }Pb9Ga6ڐy +s8]0GDDN1Zfr9̌2?Hԃdcsot(eKs8ՇklFoA_O8bm}+0bTlG }$}Sd0PЯfAY7/i|&5P nh>`Zhm6"Qћu\Y6.܆wFǚE262F6o3SXfn! Zeri]@NT!Mgwoޫ'?ˌ2qއ_:eQb2YJSʻ O3;Md6J72F7f^h#D"@<%G lB~w7`~'a L 6iܽ\Ge<ߡl4j26|VoXf|VVr@Rl|x^U>DN8F~t&Yfn-Էl93| /ycd]̈QB<-d 7&bݲ!!'o<-iD|\bͧT.K~ 3,,9xX9#3|[;Bre#Q-uЪ~Vw1ؾ4ޡ 7WWgX߮sA^w }ay5Iˆ DxE@&J,jP&IY]ݿ7o}OmY:E [78GB$26 p.Rnٽ/ ǕÛ4ziQcL+"_|HNNFRRfzVBltE*{5HL>x3ӳBÆ"%؉#_/}ӭѦwuOaDآ׏ 1jV v/w!K[Z\t3z}0YR@ (1_o_J{͜Ԃ1EdFu54Ge1H0gTl7n] JʱgJg>Yݺ N`m.%"v1G ~yXݩ@]Fj 'J5HdKгu}pB筜|iU?{)'#_1Kz뼏ٮlDvư{,O1WL '\[׭څsw%b;\\p1M9Ր "DjìgQrc挶l8W) ^}5Lؘh>nxLBp=$_>N”u ό\jh4*a0&<+gcmkA]3oyKVBmOY/͇d>3qI|q(&w皎F'bcۣ3TMpj }lF-~_RQAӆͨefN۩͝.-T5aC[}Үj, N0]3>MUCbg1y]8Xct).I.='0Tteù_Gޮ6Zx*`ҷt1aeﴰn}fc[={VD3V,: h^US-kSRC9go4x^o:zysUKEѤ},kn|if-$v'iev'߹ů#Q%TǾ8iRS iVkaGQR;}ceX+?3o9GA$t"@|L  >\A\~2i;LSۼtQ;E21#0u!9 =$ fV: G- Sș6.%3r9.`{7*ސ7* [qˈA)ȟZR5)"Ex',V8_Cc;gHX :_.~X=6+.ЛY9j {W='lԧ"gʗfZ L;.r4Jr1(+Sǫ3Y)Ui\+ ə}o(-~it-&ϓjxzP9,y'o(t1!HϽ+7z*y Dd% b0(,My lYjJʰrWj*ĺ;p,4ӡp(\("s9w5q3z >=uE+EM62L"c.M)V쩞?BH7Awo5U\c#|/73( d}6f԰&[ yF2=`wpc/#eW@iHz(SEyv+wb<"ZnfqwV}ʌ\ty,{96ѴJ`**5Yr6VrzZSA$z/މxM>tdR׮8ɵ/2uݍ[ 8^ZҒdFPJ:oqqm&qD0 ~,3C_Z#a.Ew@+m^_k)Z+;Ǐ|(!l&5^,gQ:.ʢZP>?"hΠ~`~ k,t +N0Xdqȧ.WOt szg\Y %Ydr;}poh^Vvި}Eyw_C1j;Mosu8s-,Ӡ-sâEjدPe=޸*JؾR]UhMA: Dl$/P[oX]\1a4K&zۍ>@Äf8f]ɉV 7l Tt::6.Id#욇Zl$O6-85Sql\0+I%DR.G\S$'kH/H\S|̲!T p;iL"an,~u)Z{ ߴsE9'@#0b$%tz&c^lAKIbb& v[DsPQTl}}涿ԧ!Ӥ 4i!ÈSf_%ny0e3wnƢLj46M6fZ22R_ 9-G˲yKlްJRЯkވ+P700K-zw^l6GIVX>$|.c秃0ds7wMl6`5ɵ}o۫Z .K 9T] ցʇeΘ?lCb}߶xkxc2'Sʻ;Z=L2snDuNh4xF|Ptgs{}3X3Z/OЋʩ"@@6v÷uA1۬o‡f(tޢ񘳺{*-Ac_m*3F1]pl`, >ZǾ3a;0h2ĔuEQqDex/ axT*z$s!9Ps5gf5 x6)}Lفu4N.lq?bP3-(>\Ұ⣶}K-KRDNEf 7Q]e]mܨۯwm`K?Vt c/_h,yK9 WW+C6 2g73(r.O+9r,^vύz,_fo7j*(i@'e_-+Te##=:RZk(Q<O]A51l%,Hr:u6v҈mAvcB6ܗ <=wE{Ur)h!ʗo'îf'4h&Mcه>ބ`u.[IwS ˻k ]:YZ|9vqZ҈,c\3 \~VRym |]Ń]#u R9uz("@|LOo|֬3z; 7:|O/®M+T c2*s`_0SPiDseH5!q'y}ڕ-ivx}ϝs?rJs<}9v&R/R5zI܌lBL;uwq,!$G$dY}^7F2z.1>|9=Uwfpyn}>LRo\fIk%>5C-ʩ"tLͅW|-)m~a] |kQLl);k},!b#t| Gټnhf;dYgAk5 vgO^rYX{3{yʹ|-m ;-`%IѱObi͚Eˡd: Y.ۈAmygҒ=CP2cQOE^~B'&l;+[\ծmx+ʗ@GlfΕ CpF:F>7uM}UyϚ}WOYsϟLf{Z !yKű۠}nBjaa|PR8j5 ]V(-$fVsmr "@?gfWNGp 2MT2"g6shPYh Bn ?͎SےUg)O9wo^Uf8gYLœCg֢cvNٳgiXt]дnqQL7exՎ =JNVuf5=O'sSHPu$p[Y&B /\V|l]FcžT :UbՄ-D9aW;99Ò6sh֗8g+j&w'ҞwYc[c`1x}@7tlYZWd963QIt]"K|]_l>(u0ia?<#H;G|@)Em۫ޕ )ɡ%^b҆ӊ?.*yWw0dWlrw'l-[zμűkW=]&q:+7eЦ7]@Bd&mTN%G K/oO,W58|iF!3|_9W'3#_-#);rܽqU S8FӶ O"~sN+|1%o!wϲsj-ŢEw{>+[f X/3^OqGnV{Kn.2/5/[Хqv1F[ϧ;#qTvE22yPT[MtIX4ڕw{+Orz2 wʚYū4C‰0L:Pw2Z EMi6XZx߇,:6<ՠKг ˬ3ADot,$j6Rv]rBN(3vyj͊[?aBt3lt˟s lS,S h[U糺&yC3/zG&]^)xS!G~UC1%!]GEG25ײEB/M‚|׻Ģqx{wtgj5m{e{-}^^Lr>@S7ʻӌ>Bξsuø)2DV(as |``BhADfTn]S%HT $DZ&>NH.cpዸ!_/F-t <&m4ףJ)(F溫I]d7j*} Z21̸:ܺ|i2z? ih]Bz(4>⶿J拿ݬabb\?ד(1QlU%r8:,vL}1VHZ93Z4Wa+]fBȥ|"Yf6 _)ӵdMؿUαůbVa' kwՌѧ%EVB+9T4{$'5Gyɖ8tʻ\e_\:, G19&۳X -JթrY+;IAl("@|B/oor :i9+",NvG6_|C DU2n8("!wPCpLӆ=˩eISh^T M ڕFʵ0 ԻI}-ȅhX ?rA8;vG'Ek?n4 (T05l\G+[qhc>>abRGNkY|ķ+4idulz}UiԷ+^Zu5$֩t{ȝI 0KMʢ"f GժNK.GZ5xO\iϬr|̞ٓwVg%Mj6 gUyww?GZHF9G-ÃSq5*.P DhF@ŞEٵXE-6mmq/>KdMcC}4Y_Ee#e;g--bg+csAUoȇ0KvShIWF]ѥ]ɷh5O3bc`vqn?ZS/uˉXW m_ ɿyQ̚UdlETq}Sq(~x]̆-'K6X%;ݮZcKй t-P6Zq$SƘ_s@$5RdBTYo`l'؈Ƥ,aX?>2̥\/:1BVhU.》ʗ_3EzdZvH3ާY): ~l |O~ fp_YG4k(6 DdNMy&9#Oc.e|>-8a';]h4X&Xxi?G.BYٵϞ/V]>Jxװ{d[Mŋu$]?d{қ.Ѿ:/GEn~} 2ѕ;xG.cSS ̯7B9Zj,,DNFŊr}c ͺzy+H5{ge}na;Ӣt]fGϋ`)׎t۽rhOCx p덹z1#=R$mmfAׇUk^3ŌNjp0> W pD%k!{4ajb}?ХU7f`1[#5P'2jQN} H;;t_2od5Mb]r.W6UNDz]1J)=޹U}%ۮi8tn-aD8WM }ңˢR٪xe;~ GNDžkA(X1Y|eNWiv65Ži .bwxy?OҖ1.:ۙ1 7igq9<{_CpTU$3PЍ8bgw(Z>W/?Hw' WZhlg4?sqJqDQdGV+aQڽ pA̛x4DH\3덜|iQ?sZ1azG_?`WuR$:Ee3)xy7Sʻ9 |h;w+ܳ}cJHn C=w=8klJg|vG Πc1h5+ݸzi?7! Ddn6#d *>ZFN+JpOXBvf5\n>Ҷ܂AKUMaP6G훌#S:B?F(kXzpaԈ}5Twp9^~J*gy$e_rs9q u`Xn.5*̜ǚiP~T6<{7oDi4ШhձL :g-g+]ޠt KKL~h3i,VYe99Ӣ>!0 d>F;qQu :FCBtz<@+} xu~X_\e&n<R{N:JGm{xWOP0b)QٟSoIYnY;!%P:&c̐^ңyl@ h4 I+G|o[cz2bXPD +[#F<#Ie~'\ Ou'y{r`E] 5D$4ATAPz3&4*JP4RARH@D5@\+{5wIr|vggߙyvgg盖}O_ϼtP#ӻk3QƞNyu-ۈ8Nx$nRQC04 xs^lzGVO6$`@~kQTN\X!ޓx3h"@ܖ5JT5$pʔ #NU_aH8J,g$K=j̯YN[ S^Kw/g_0CC,/k)2oĦh3 Ujq ޛ1 )&ìD6ON'!e3$?8sv\&5 fGGbRbET5خI1sWSFKM60oj}Ė|tr\vN$Y`p~l!TeZ/9eRG|xI,öJ~]Χ Sm#lKM rwN<->laE WGE~,O\x+[0ZV yl׆sNK<ͳQ;VJcfciJr~rI4p0f~a+"V YEg9UXR`D o&h0s](T$^=SS2#zr+V@;K8g|ISVeZOM& fV kW~~Hes]>qtz_DMZ%hK 8kX\C`| 톖uBQD zARGx?:4D6E|Vbz)p&~q@œ*!`@IDAT@``!Q;l$Mx$ukVB",/hW}\9_N^u)#wR9A%"?x;?ToZ^VT&psw(D"``-L95IlKtlœ܉@A&@ }J; D"@rCS^t($w!0aWX}x3N>(`-['e\p4S< Dhuq&^:DŽyRD Dw! UGnHLVYM]测lu_; E5TD>w-lR/7Ia"@ D"@ D"@, 9jEP&O#7z'a0=#Dw kH^:@܍/w#"@ Dx3#T!~wR2mj+2Sp| #_N"@-P23L~D"[AVtp<S D"&hqK7hLM, _]DţX7w6ō"@ D"@ DJ S D"@ D"@ J:;K"D"@ D"@ DPd.7M D"@ D"@Td;K"D"@ D"@ DPd.7M D"@ D"@Td;K"D"@ D"@ DPd.7M D"@ D"@T*OM"@ D"@@@fh$%%!>>>ϣD D"P{wx7(SX 'ЮYS_1i$=SPVNk^:<޶[o E2EF n&C!Aq8@H@'KGU_ τC#w^S\v y<ґEͥ]w+ 53^L`LOq W$Z":3:<}!x+23Y6jq~:}fCt]?deyXm~ D;aaaZ;筞҄ wJ h~xr;D^dկqȒ[jJ]mj:M Y1Av'mƠ1f;&‚8_yXw֑v&}?Wi<_Ň.Zuur%D zoґ18{-[{Qz]aelmzynᔮVSNetj )|ܠ6(hDNx=^F6zst7aXKd{L-ڇJ3F՝^U?Ie~~͕"@N^IlF㷞W% U掅J·ͣo%jqFav*M V*F 1컟Z#x.']#/S0P;F u0`Ky8y;5!V˿2LYE`Īt,ɅO%Kcя0W~9Rj":Oqrj}zy"n.tIA '])2CQο0 ;܊\10y8 '.^-{h13~̤/ՙVCRokZ'_KD"@u5}0Bۄ[YZR*}[DtU>m/NУl.&S HE P`*`мy1jqQ>,t8$Dɽr.] Tl<*>+'‚>M3?c̱KhĥK8{1$DfxE]͐~vXKǙ_"@7^ ^:'m2\q:f v%x~6hVxa֑Q Eܵ\bH{NK/wtuv:/Pldh):1{McJGDx2^ N:&D;{FomعmXl9Fݵ\6Ɵ~N3xBn.!`bѧSV`S%~-1Pt 5} ƙųC D4c]~8Z[Zs{V#zԔFI8 eGfEuh b:dŨmNË/^r@n}oAEyhxF~k߻ͳtl@'8Y47j+3.~b)tX\sއ ͊^>1p΋ۖ6 Mf)NalNnQRyI ( ƵKs~lϳchx8Z־0>,ACV V]nކU1N:$Ui?coӘsq08ћa:PA0Yt˝Vx1AZ\<y%ʧy&D8ɏMSC7iU—3-)&4]Zt`r/y-aYeUC'NNsӼtz+Sײ%pxd'DszodM&!H.ͿGJnfH;/}.S/ͰQg(sr;+EjxAyo?^o?GuJeha1;,Z#g7pjYm[2- Wvc\ &HFAyv /G_nŧ3ٿ~i^A%Ú8w~0tk|uZ?⥖Ƣi_eA.h8'--7-'GHX~2<$air V2_2[ыP:}cQWEU|; :Oo"Q~(ehX.-UEa}VuB9cd0D͈C -c nc&kl0,!!2+<ώHX.g,b*X63z߀LJxi+3zS2zL܊ NOEᶟ`/Gn9q`T*_#ze^:'?]ӓ+6FuT"U`wMoɘ/78uAdߘЫy譗6g#D M8.S{_i(`~c}ҢK8*VVS|YRR nT#"@%9wC h13[?Er|c숷tpU(*(ą!¼!rmL<*Mwx AzaC|_ 0ۈy A³'eCҼyQJy/ULU4| 1R# XeB`a?>a|o6FұIuhY+LTgKQX-ˆdBHˌ;u1Uozlq l6LND珷ӸzWAa158ӯ`&Uu]ټ6*F޽s3C堑"@@nnaX|"EOh#CfT@(PLkUy(2^rn^8Ef&I.0LNV_1rU80 #}UTPhg_g=9C}"@<;7&.mN[1Xo-텘xQU#JpQ.30R_σX1Ctm'b(TP+şøUJB:/6aU;ٴV~hwL#w!_X^74y޺ JV1a0`[WGC#ouF&T-S-YGcKGlU 6RԉSsvDוb̸rG6@-iKw }co@)]ʄeOR AV[|zAҕ&4("@Elۼ!Z2ٯg}S_٨!kM!1X5̰0Rl>2h6@.xc%w0v&/6^#WU#Y7K}^: "x7\)^-peef U3?`RlZw岒p}gj[gsNOre.ThX ! nu ϳIzy阈pnaIKÆϑǎg\IZS GBi8vå7w/[#Iy8Ruh qaX0lOί:7oX gqA-ud6u%3z Bk/Zp XG<>|X%Llh7 m{ .W0*lv("@Ek^9Y6,j{thݪ+% ^2ŽrZ ӍKU~tImQNg "I C}]Tt fC6@?7 !.I=Aʧ: D\5|+>kE +ovR!YX[L oVh Lt 1( 6MJ+H)vgsI 5yQ3cIo܄c qKlz,.o;cF=5*)sxwtұ 9"x7\y(,pV${IZTf^i2K\![CprY :CKfh"%/1o:h^_aΫ'a/ =cC3ƒEj…0x82]A@~%뽑0q+ȏuSGO~캴8eejw,)jlZm+~%$ o~Рx2GP}0^:<:zOv/B^:igX+X\yD E`INBRʠg=di~x*&ʧV# D029*sz5^g6&[|#5~-EhG`ǘFb?ǭ0@Pܼx_JU(Q)0[ڼt8]D@$aO+!&/?ޯy~u5$MCTw)R_0Y# t9#B'I/kדU?i8@<;3aqf*{1澗4K˔Yx~#,=9Kǁ|(::pJ<_9*!-ۊ!QNÇYv`PRÚl~}aV D$3|E3l//8Iꈽٔ=_L>uF-YNDݦ sP2'~M#JY zKNꔰh]'6T O@5=sGw0J Wqp9Xrye T\?o;`ʧYD"T[Ue*I8Ocvi5B te0{ LxㅬΦKXc4/AǙұ鲥c>p@+;jšc^:FY;Q䥯&ak6GБSq2fx;ˎa˟{M& b^Y^:hwY;b>c9޷"5~Eh?^E(Y q vZV2o^V*x#!_Xڦo_+Mխ' @}YvJE3wtwΉ _[~xۧOdy@4KD"@O@;![,R^%5aǕЎAyߠEiiؤ__XcЎzu~F۩ŦesM:ǿ+Ѻu$ ,ok%C';T,|*mVұHvUGOѻvwVb`CiaTmD y阇MDx.^ ^:[ ewqh4mg5XrȜ*e֮"SDԈ+DM]o6Nv]Q7GbYL9o`x \l!stycm^~oX/ipEdڗx@5;)॓s5RSk;ѫkˋAd T7mCF\ǚMDL dnauA^ Z2Ey*4+(?D"Gv;0gD!-3K "1"aJ/RϭB+D̐Pz̠[v \wP:0e-y.KƶqgԵrgOc8u. @j͝Kꩬn<mdąbCWd!<^:qn"qziX //m%*mLx1tkuA0jQ9)(R>vB03-dϡ qcL"x7x(]1ewUۿV{ej -5H^ĢGpr\D}bڶ 5v ;?7)늎!hXZp  pH~PAS ^p`o-^yc47NǮ y:\ѡ%']Pn]4]əq&̼[F'2Y>I0BkaKYUo,G|~B*,|OoF`ut% ls}IǮ{p9v7+xݿkQ~Q>q D ߥ+TD YOlxC`P1gi };0;+򵄿gدxV(7@ʼ*. ^Θ&Uj!oi;~yc'STGoNnzpeOEGzat;.YGᯗ51F 3/;A)"@<:AqF*h7#gD.U/~x7e`>R?2KOf_GдT# ;H/e k?ȼyܮϳ /{{^|Nig뉳=BiC˔d~:?{*[O_)⥓uHCVWֽ"HV=V*ZY錨a(}.+2ˢnKk%ONAk,(q1B}߳YGqS91\~xO-9"@@.}W uB,sr7MmZJڐ6chDkZmʌ8t:\eϛ9U^N1qS\?iI>/YԐt=L~"qp_tygaҳԷOm>t DSzoqUvðbj>-gON6p"-66戎,xn4[ 8-8Vo17Yug7^3/sVya'K[-N\W2N_|b2-8PD K2MY;"0w.Ə_V9yr:G 9@ 9$D"@`J;1MfV5tti(('O D!;P(D"@]Zb\K<ؐs[/%ۦmCns3FMrI*[GkDD"@uJbW4,z/Aŭ5w,P"l/YTxքjXYf./2eJJX ұT&"@<@7x64rw8dBfN7\ƭ{~ ^hQE7p2# >Wfs6<%"EHP ME'j쫦c5f ͉z ۚ1@B6X= D"0 ߽n;Kf4z;GG~=Qh95;/]oap|c Fo5׍E̽ZKXo  -tV螃 ^|x0\Q$֭$ܺ{-y,LB/)CǷLjЗFJZz%]{pL_ gйṒjqzx> v>Ju1"87xtҺ(ul%&lָT}σo4%)y7͘(r8#2FTn}n4l/$-XTs*VKt̰e}pbwN7:p& rj^:KUr,ݍ.,ć =P^G fч*"87xto*dݜLMqȓ39yBh''kV(wC}GK Qԝu EɻAEfpJts̍Y!D"@rCohoȸw/DŽE1ǿ SHlGe4%QR}#s:41 A2bTtΩy#2nCw,cw4t Ͼ?Tܻ}6ұ D8U^p geq]a}m7WЧ3(SoŠտe+V4+gkӐn\1Ͽ|!QuJ?7(^8B$Ϧ sm-kh;}Z^/F@2p69p\ƀ\աe$IyU6L*dmhZń"iipx,bY(z "P8db$?>/7O GN_ܤUuT_şlF0 Тk3|{ϋ/n B(L7z ^\;m:IO1nP\;}[6oVƏxDxh_e yYPy|1Nccp(ge<5S\u 0zW&}cQWF\ŷ`T6/Ho|ѼVG)#Xи&Hȁ"@rCC>,ٷxmDDpGqO[i.#EWjo%yTJd]vŇ rD-ش|0q$^9ɞt~u Nbm =^:vSDxg :OfwBo`yx<[Lh(d BP_8/CԌ8,7K'/蔍M3.ղx H  D`;/d&l&[:vO퍧s;f=NN@S<߅kF#"hbF>+NT?3; yyWyYdzWKh_[2zL܊ VLOEᶟ`/n=\ ş#f. "6Otkk6ҏ@ ke=: NݚLƬ<^hs>Xh3p6bj.@&q)\/4F}a0|б>i%itV^]TF_T5<"@rdIΝPZ O&_/;G+9]!\*6 qa0/~D@PY6NlgH;{W`KǁL!emDؼKϠZI3pP֋uॣ(<콡*$,&7>pB7G&+@j.,,r?ߌw>]K*3)EJZJ Ez~@waƑ&x:x;I;G@<R>xI#rs hm+Teb8bѯJWmom|.n̸l}^|W.A,ĽyQJƖLU4| 1Rc| 7`˛ ܮ_5GZaE(:+2aX-T{ױ-3_W\FF@ocDtxIzuV c#l?@Ihh-#tҗ7O]A09I}sıN|`/qtN# S*U-σX1Ctm'b(T0Ӌ?qCךy5\+ Ga6/Ovgh_ oW q-5E?m [t֨o (KP?]N-ŽphDj5FT"2Hr\wʧFK MxJ;)b=- 7?<b@Sup ? Ϗ)Md Je m\"=_Ka8iM^:Nl@+=GF'֫F oVF ϋtDA!D x{CH-fFo5RdUdO;]Ex]N*Q[i.o3z ay~( V2ˎ]:R<3R%nCܔ}h@v:N0 p"'wᛇ0bY%aWUc ϸ~ VA^/T`Ȱ7nE)|X%Llh7 m{ .W0*lv("@Eֿs(^mQFgZfW{ɝXzCCn=b_+$ziŏxS :>sw&JfjU^:΄iWws֝عu+{vbߡj'X@IDATH'^nbTS9ztzKp7B皢a9?!1p=L<p"/N߄ _m1Wunݐ ٔ/ '5@/ZMN >MqP6=*-Ac2w65V׍T9TDOtq,MaMd-n :$ o^/@LL [*IRy~sbrՃ s92A1TEBXXw@ʡb_N}*֣I Cn5RYH_.mk?D%ǹpC y@@A^X+-bBz)h*E평տŴx.f؈:sziRBtX#Пm&}A=&ȨY/>07!QP1m8m2X6,dێƘѯ~icOx}lk?bżt,bBDx,}oY%Ykx^g{~%YN#=j^ЙM"_;x(V7|&Vjx,=WM<]64XlVN=e%$B˶Nz}Ic")y DXL?`Pu-$c`B}7/;Quٝfwh6G߈Niq*PL@&_R23z;0#_oy/بW% G?G&wl4yWua?ڂ+B2' qIPy[tlsugXrPV!-ЖK "@+`gnNV k-9}M(-Rf؞>B-Bin㚇/3jx|o%~Рx2'?{;9/4AUP)8rkʅG*ӯn<d}vy7[Eb{{\ <) >/PxP~y|Upp?Z:k+OhWB׶`;Ib8MJ' =ZM-SXȑ"@rdN+>O=doۯhmH' Tڬ͓jOC8K'px$PR*żtxpoN57.el묯p·=c^k&t=zd;VɜQxY}]$ܗ5Ӧ~֍q.wN拌hyc}Y/t5~-Lc}A[n Ctf*Q/xPwӲO!xuE=КA8zV37@=_p#Т 3KXf#ߌJ]]*l&DZ\ ^Z_O"@C*'eD!0ꉴPAZ߳SW<ۏ_Fo e^jdzB/dgLvF79w--aB^|x؉c2Q[2iSb^#/kΤK"AhQeMn/hKNt#{WyKGuh >P{TUv=x,\]de3T+s)ڿI(񯀪u?dIeֻ̐am7u=_7 ĬWCca-W-}xYv8?l=qW~0~u(,G9Y;ZVL+^e$+JG ,xF0>2ˢnKk%9ONAk,(q1B}߳YGqS9[!Zr D\$PuؙxM+z^ wBR҆yl{m|X MqBy39݋)>&n'M09);G#jfKc^:4 M3~Yo'6C^:YDŽ|")x7xt{nqXw+#0acn@x g Bl{r#:BqvI5X52AZ*Hd2n]#pEa̒/CnΘnK>/mh7ƙTѻ,/P>N1K %KғUDYG6ѡe;To Vީ)H1xu#;4f5"6Nl?i\N4$cn'ZhQ>@BD"aaa\8<5P" ~*-Tܽq Gw/7Iي&<úU+t@^iq|9yEK--& BxsD!W?S[DnhY'K*|0 C!xE`ὡiӢѰzcVӤ?o„E[r涞 y. fV kRS yz1Qf[ E@xp횅 &z Q; Mtj]HO;0iW WBCƳ)K}qTΥ٪3DAD"<\7|;E"@ 94雭#x8kE _~Sj D"W*` "@ 9I@[xR GSJ D"@܅NP< D"@ D"@ D 2|sH"D"@;L$/wdDq"yMW^ "@ I@Z3G"D"@ TYz 17/lM'@o1%"@ nAt@ D"@ D"@ D^h^$I"@ D"@ D"@܂6P$ D"@ D"@ D2|"I:D"@ D"@ D""A D"@ D"@ I!D"@ D"@ D @o  "@ D"@ D"@E ߼H D"@ D"@ D[Ă"A D"@ D hki]ʒ4"@p;M|&V!## 8k6hLM B Hǃ[ 8oSy`ٶ[o E2EF n&C!Aq8@HGpѣЦI2G]1_ѿ"&[w}r5:^1T@qogșH8H{YT, y[k4=4X~d|V',w&2a,]W&e%VeI$!!rWQ#(cB+Yyg=f9]zy<=5z&D"&22ȁ;C,U` 24_ͩ/Ou_j:]7~cUF}oy0Q{W#ݗƁ|K\gھ}ڡNDJc^nƲ=17_=/9]d-}3_ko}?ןVudK@!% ZI6kvZBխeRgjuk@_ ՍFXzgMS:e`X} Y^윯jO( 𒣔9iU}·Tm-k^r\džpT,˔ގT',A1lyI[J&"@ +!TD3uƴ cނ[brA}#9c3R|Xs>O)YzC(F^?Fv*n+VA `C+S $e}b_2"@ V|zc&S0H ˳ZIVD.U+!˦A-Y33Sz˝u4 hD=QU0pl=\uָ" \XqݼsgOAzѸfY`3_F(7sfZ/9r]y;Wp& NN)(Y)Z4#~h3x}3[Tf3J- G!T$1MYq6lL8QRuT{$J௓^у,ooy;˅+ۇ7 &xc|)NF^Ɏ>$G,W!+*"65el2Spkyls3%9$ D"4߽fm-B-:鐬=-fnRqa%))H\=vHgpF!m0eT]tvHr/9\ҧIիɸzc/}Uql926C_9.bB|qJLYT]k6-Js2ku4lQ4JMxɱ="P Qyn}*2-yRKYgQzo˾?{˳^2SRҲ?dEY3&0},8 ol*'T)D0ʍb٨kdlSz[m+1PTHLcD*)E$t'D"*>̔r?pп%Z8ܳqln+v:vdVGK}yT<" \[Ga>7&C7/>Xas5FvjW}F r27 [^*l}Qoڋ6,g' rz KD@a&G;yr6C-)=Szl<[D;+4QinfKӿ~N~~L [ۇoI>U!3 r#/96$^VzKo@ D50 ءU751f6 v:?YC0'cׂYjM@\R'p:^-3jQ^|xɑ彡iwMu؝/9Ά؝buC:n^ױsoyɱ DpeGCX~'ÖG0'lڼe rc>2%pQ,YK~x*Q^[<;$@۸t U Gkۇ/n>Ukd|i_xŬӧbVvlt 1 OPIQt|sIˠݖڪU\rqG\ vH7QG 8ۏ[o aO3wqdqj00ԟL0cAʡyhޡt̋/9жY-8Mpr\yyz{BD.;ʞmXT 'K"@ %CS^!PN':ICߣo-yR||ڿb9%C2"X1 VO)[l%GeoӲJm ۆQ=X("2Dsn[ۇʽ|j)ýbfGL kqs&N߾q |q6Z>xXtÀ QRrH^y,PA8r1ur.12C1Y^=.Z֡_h17gD2Jdkk8Osen0;/h] Ճ6HȂ"@S}+>S_AKBttJf$$>뫸] l$o\Ƀ:ʋ/9c-[xLfdl[fřpJVx8K>U v4%AFНT@piyΟnu| U$ǾD켳3oCJ f.=5 B/hD w .xC*f؋ʻ;(<F('JWى|Cr7*ek,'os,ޓ)[[ b}Joshϡ؍Xvwf:~M4/8Qn ZPWl4[RE5d5|_ᬬG€yFFֈ{г0m6; 6f \D]b/+/4F劬Q|?١.i1%QtRst2Jv'AOD"?dMr'|5?B+8wd'7 [գin@uPN%.4> RT C 78D4,*wX@:/9Ne-Vga-G?GJ =w6 ybW\^rL"@dԧg]hrΩæ;W0.U)ش&;G]c!!x8$#ROeL(Cw)-E& lUAv6N )3_D tΔ<#Z֌TB)YjȚel/8;/[9y'W>\xfc4iJ=7 *<GdgyG泹<:Jӎ9&jBJMv?T$N M@]Ü߱`Edڼ føBEӡFm~o 2#*t؅uR6?l%v,6MAYFV/D!FgTn*Q]}L>͋ 0nM[Ј5L]rچ!CVʾ<>r(/tBM]jg]4멙#FuKdb#Lj!廼^7-aV߸C̓a#-'FF;tXu%4|*; D7W]>L-\._xX7ɂ[ޕ󶬈 j='t3uǵxG#/ێ5=`\KHRލ>j{K$ DP\uFgB'/I*&sƳQ9g@LzHn>3\T8yGɿ`|j D  { Pn]4hXQreB=Vv=_Q @p`X'sfFzr@Y[zcyT-SzOh54T^r\ ڭoڸ[7ndصnʳaڊv:Q/9<"`p<>`y)X'm&-~ϙ?y03qek6ّc>[%^ʠn.e{myzzʒAoz*%`CA0m3o|`9Kۇ)_ +VUjtF&ə`Jr,Cc&R |!T/}wDűs.כ;)&;/^e%(-ƪj/*[ aGeC#(\<, ~W@S6!{93Hg#s8y䯞yof3s%S""@|U|['1/E:cXþ[-[1$"^mRˡdȸ{!V-ЃbnfRؑLvؑ#8 éw>{0{xwxL4o nO5DwWkoLRH@BgyN}*s֛Ta啭IJb୎}MYA\YO &9EIs;UuQzPA?X]P{D;۶D; q6;0١j*N$7;] Z%wS]D"p?WŷEY;&?_E%P^3anx/ue)H|?Na WRHp@ȕN>EPzh3޼8ǯNlWھ̲'Kyq)PrL@!塓=MzZNwI!I1/YCtdqɦHTBN"@gޣ.,U]uH\ GW壘w#ceu6EgaFp9yRC 3_ z5ƌ;ZY/WNGt= ^r\|"<̭>eJW,2wFUpUv'ަ&t%Oi31b[k'Wsf!нf ^%rm4nxF`+_an4>>K}ǼekBxB[5;?󅷵s˧~{|a&wP&Y(HCkoH3G +z*W8~WH)s>1o?!-U&gl~C47B"@x.7;g*ҕ?w~xv(,{~|'k=Iyɘ1c*Ƽ#gʎQ,{ G0xɱ'~@VH~v3/9NFjy]Z$މǻp^B;]Mi0u$"iT-+wWN-$AYuRvγrهE4F6knڀK9'UDӦڄ/}D>Og|K <($Ynx2{,~o+Ual5^[T vVi Qo$2|9sv S+iv쨅\(抈"@Tz RѫWJIXR@<ۯм|Xe@,=hPR9DʶlbYғ#ϷOCc炾;|*{xq']A&CEN#&f-K1|0ߝ?uc6="Px *yէyZghY3{|H(̤? \vd6wm棅&[yW:fj,}E;aG;'r&b`TV0XUR__r=d0=ojʧ=ty"cYnjz G~g2xnWn{XlzqiP[Xb Mf nzJ^].hUE+="@@>f9W:Icn--jrtD8S(w~*.>N zLYe}Ao)4w\I2D;Wk8q& 7ߨ߹?z{D1S f*MC,q!R%XGAx>n<1M2XW6y#MWzPv^qجp61EE1hͥcy6J^rНOWyȳ>MerݫjbޒX@4+3K:웑SD+rHi˳7,Ӄ޿&oZ+wXA "DO,-y"X4\, = 8%\x"Q^ >pJa`+_,}3zw$_(_JgQp3~ya m)EVĆnGU;Dtھz5ͫ><֞cIg?zY]k: :$l;Gr٬Vuæ|*" DN)KWض,ֺ4 y=A_CKoMuJ©Fl߇PS? 3̻k-\{p^rD5s鲔'|ddͧbU'̒5KQhA&鑸z ->m`K  TczXMGkkcFȧδgpvf[WoDm^ ̯Aٟzx~_nQiaY~APg!?M)2"Zft?_ڇi^WY{8+/,_$' /fdխTn4($MқguyRoYԉ~uTқvx% ]iFs19aj I {P\XN@߹`|j, D  ؟>,^*x񒣈Mם4'zd*[B/9p7]~k{L g}*aWL锴Ya,Ƙuނ?q)`g 72.ŏ͠9u{qZZM1:Ξ RW/ >O=)wUN^Ŷ@>Җi"HɔLk3ڏڳ3r(+v<ˍ%2s;Aaȷoz*ִ>{3xK˨hhcq6fĉzr ѷt|*  DHyS>D *;n8*V@`lN:n\:?ً!tmGVB1Zd¹/lFRzqQ?4biR*3Kä:-kG|ɒQ;|oΩ!xɱ="@!> &Ǣ~2(D3^߿ǘ,z~b3؅eҜ’fB*(8$$ʊ\%G8AUZwυ럤3%֒"@P'&V`Lz!M^n-?kG]ZJn4r;Dl (.cȲbg$=2t^r2]23_Em)|KF9kڲ%ǥ;plGȲL8]>Ay*ZÖKd!DȇrMeM+Oc9}0BQGsXēu&GWkɔO,R=`A&rA95+)3w . B+f] (n^4#tŊ#q`*}啩fz[>/ DuN+ Q1)BDGYF$Un"j*sɕeӠfҙ)MzA: 4x(k*8{6~͇>,( r1ݼsgOAzѸfY`3_F(7ssIK';Wp& NN)(Y)Z4#~h3x}3Pu/9"Pp .z~-sycZ/_^Վb]_ΔLagzx;~ܙҏͫd^eǣCf_ ( @@s}8xvƘ6ebq6lL8QRuT{$J௓ yb̎>$پG,W!+*"65eLf n0<6`n&$d D"@&׬MxEECETz;ûPJԜZ3.Q8d "%G"vɷQ,Aˆ#1u$aG^r$4)z5WoŲ/r=?0)ͼ8_&f+g#IZ~}qJLY 3zX#tֶhQʬ. ҰE(5y%*H@!&PڔuvHS9tBKQrVh<$*qd؞qVZ͙/}LnX= '3%!-J Qk8zu[3p ElcٮDnԵ?ەoumX#cJo9}`!JDi#1AHgˢc"@ v8Pa(ن-evḆo֯ؑ[a_.Q#,ނsn ^U;wlj ߼cͽG٩U_ ߭enCm%ǽTb 9޲mX|N G;q;D;/9RL 7Vģ[#%r[\N %jG15*f` UjI>&{CPw418_?';K?~f2mBX쟦`fL魲=!ob幩I݇0|(k D"@@^pJ Br8~]co@ bΊ1 C)q2v-ŞFϙn%u 21Ce_8I'^|x$-.-fƼtPrf?vglx)V7; "@ !Wêw->p\ymlwa>2%pQ,Y=Өk/ԭTy@۸t u0uLQ%J2U㮿Ŷ U}ཞ=͂} ^1U8ۄq8Or($ }LB cAwPqdKO|sIO -^3P #6Ni>¦ 8ۿ_{z!| >܍~Q1윒!h uƔ-}5Pκ 3rnFcT }֎SU8 ewt)¾O ?=D+.lplfGL kqs&N߾q |q6Z>xXtxt@ߣXX)!1dl]ٺVw%^ҋyH66WǑ1k] gNjbxgvtOjHװdΘ3Lh"~% 3_q|ڬ}ؖ~l@/h] Ճ6HȂ"@S}+><̿D-DG 0.$nFB^s;:I޲p] ;wg #W`<|cL-߀lJxvPz#݈*f#D{p J_"<%b睕ECåGA+H>}_!0%CBpH( G^I$^FfҊŀ*]G|K hwfuee=M־7$Gc/ Fk;ؒfam<)@Of`=|)@6hzwv(woFbZ d̿;n}+bԪ a0[I{x՞-eH!r,E5H/8;/[9y'W>\xfc4iJ=7 nچ8"?;iz0v.Jӎ9wjBJMv?T$N M@]Ü߱`Edڼ føBEӡFm~o 9+[:K@.l?şrdyq9`!+% h*1 N7G9LGИ9 rDsUz\/gaZu} KfO!Xl 9-0*xIef!9O5 W|X#Mc@;kX0GdX6G@I>NT]φĺs֞$5h_S^1%'`Pmr=TQ`e+#__(C8;'+&$c>@Kە2tcss5k,߿h xuzJu8[4)w!oVswӬf"m9,};Uχ63"zM`޶Bz6\5.?dJr*m9h0>ZpA?M~($N MzJ)a#•Ÿ=q3P]]ğeEdPX>!3Cl_#)-ЃG^}k{ُƹ,=ߑh}?-+UI xS~?[$^籨7Zf{}^}I-:o}wZJ2)e#9r1qyXΒ7r]MM+0lqHމ!CWyd quGn &9^y#_aC+[%8vWj4г~x?7JGFjB5'}-3)hE<}0xh5 Cb`6 Cb򌂅 [JL2uPz O~9#W [zdi2g<s D<YM ʧ8"@z+<"0F[.4h`{,uo (2ج6|AO]c{nP$3֭ A~ԈAO5k+^o,eJ F]K+aZ\q+n~;k $ݔg/1u<^rDyt'D"NyjO&'ʼn<*Ǽ^Qe{myzzʒAokʹzI>[P.gKc =B_]ZuE򽲝p/Н̟͘KsL*ri^SЙ`Jr,Cc&RB_"ɂy/aSwL+uI&s&~O8~rtq"a عU HNI1 *+) 2ZX^oUE8(!ɺx6Y< lBZsftκGvqm_=(|gKʧ&D DNOblr( 72^Gg]ͦ0/sKaG2aGB%QNcn.?&{S 㚬PSoLRH@BPh,Fw,'!<ݦpg< wzIhb`HdϞf,ZCe31<- Z {bc햓X}<*-ŧV9Y#+,39dΘt ÚS.`қr䀏AN୎}My3N|g`ԉ+*ygή^ A>?L]P{DHpǏ8dz{ [yMRɮEͬK;ʧ D~owLLl&/]JBT g _bHR6&ґz(~>#;@p5z\d@+1|(f3yqU_:} %eO=dRCp;1YP n؀ 9UXynM?b dϹ\)Ǽ^ ̰_{*GXpN1\o|vOX8G._^$ÖAA>Ww!p! F DL"@;UL:$5N#B«RuufXIJu3}0{^ˉ!Kz~@&Ik6;/9sg_bCZ-]C&Dx1! *!,OXdWq 7 -UB\#B96FcQQvwlxgJ 7ϰe*ACnqp|򐀾DK}Ǽecoy A&9? %k'oq"͙CdW,wF)oYIAk[YjaT{ %E.~I)@hLsh)b!K"@cf8rV;D$?6 Q)_+]c0l@' tIu$CxKNn|{&Yաd%E9%gH .SyKHb!]wo'#U2sX lZ#~Iߥ&5&q~2OvX_nsˁM #^}!r |K/?}vzJ?ԖjzNHn=)nʑ?Wua|Rϥ\Ғc!/X},X)9}B=NF6)q MJkc;ՃLȆ"@򔀼YHvh㥜39JzY ]v^İ5/*֌e-t*3d˽] W$Dx'OÙ1/bSSOV߾+֯@psl?jM@ģzylz9t .bT6[Lstܕ˒)d({XjVeEuB# My_6Pɶ?5zv,(uW/k0)s] TZ̷=( F57]f$"2 ]8F:7=guhz,;Փ`hȇz/ř'_"ʰ2L"}@<ۻPM'зV-{Re`^㒎g{'.ʧN@"'D"1QN|"Qsg29iwbJߝ3bO:Xqj;Dɭ[{ו޼jɱпHf&gk'N=T`&Y;좌,azei-c KˍXg{ЎZ5h sVۉm ^'G;wo'h?}9wU>N L>6|c/yQ ĝt=|GUM>HZv7b@}wO| XMD^yYCg9W;RdqY# b&WkY6|қ9^ϛWMG:~Z%/CnyeÊK+bs'B 9$ Y.f)Ӳr7DS̚ƻptlRҁn,H=/QY< [e^_}Ҡiebٛ$mu|w!,6ﻮ՟VS @ L@jwp?dg*w$U!nSOV%TǙB+Wq9?wntc`ʚ(+&xKX$H!ޙ /_ÉÇ7^vFѣ#_85SYh(f amŖ/u>BxtE7q$iǰo>>JtKnkڵˠCf5u<(Z.tAHo.ͳi$#D~yUKg9RMkqdO,-c ?@r6>(.쿗p DzMP+TVhJq+'Mm3]^[;NDn[ZTX0mB~[~'B[OPR,Xf-jr&% ;J{釴ݫjbޒX@4k󋗰7#7 ֽ$ ̓Č헼^mO)r 6t=%߷HL# l^ g,O=o3Z]:+M9C-{2|nݰ(N S*b&mD#$.M>(C|!Pk  W.5Ww) =}BzOo,w3﮵sW>ÁG^|xq՜WΥRrUΓ5GT0K^r仒.Fiᯧ0fG15/9WDRyYC9o+ZBwK [hX ?iLmxS;J 9vB)>AxzH<-( lINPaV6o~r(Y,HOߞޯ~t&ࣵ1{EQ`xgZS;/%/^m0*;>Aek*FYHԘ!(yZҲ}ˢN+fJ7˸u ScH®o؃b׍jrEmH3ru{qZZM1:u%ΛQm/v,hR`>p֟6)SqJܾ@{/b[ iK4Mgd*'Hb- vC`-3S݊bO~Ֆ7cIl4Ƭ?eLmZMO]r>{3xK04pxx4 8{Ypost{WMxB-)J(@ &22R^=P28:#4tܸtl CT; VEc2ɸsG_،4ۓ7DƧ:B1h.oU/(fJ՗bv~+Z֎@%N?wx~SCc=z$DNX^pxbOl]SXL\\~gXDYgvh//9﮽ 22J5fKS8tKvoNFPz=kꐙz {`޷y#$(yzJ &Ǣ~2(򖐿 Wo1fO|l˒0\/!a~f?t[}fP=>L wŷQ$D"@pRa-E"@ D"@ D"@xNx$9D"@ D"@ D"H"A D"@ D"@ IC D"@ D"@ ^A^(D"@ D"@ D")y$9D"@ D"@ D"H"A D"@ D"@ IC D"@ D"@ ^A@H"@ D"@(Ѥi)eHLL,4"@:^xꐕ$u:y&+5WptlXÃeۮP3*eAhRq%ܷ kN8/9NH(xyQ<;mlWC㯅-)DG L}:5܎/}и'|@7fgbi(;ޚlV^1%!DՈl/_e|?/%R=l͆ D8"4:r9A,H; Wsja7mSzڃN_1XQ~&aEԼՈ~q.c-稶c`v׬l|OMW}KpW-lqFj!nOFC%G]:"PH p)9S^k*E vg{ϱEXej4R.BKl e7M8_t,cJpZ h[ D'Õnk)> Cu\I:s[[rk- DujbU -W5; -ڲHp)z5F#k,GNĦ)P20>,/v %^r2]2N_Em)|KF9kڲ%ǥ;plGȲL8]>Ay*ZÖKd!DUA3uƴ c+W,Хo{h*fJw v|)Y:H'"jxyQ#li7fbXg $e}b$D"@ ;߆ޘm"#,Vk#7s\ګf4ktfJost&!7 9Z Ξ_W ];.|X丛{n^ĹpEHѠbh\,k/O%^<;Wp& NN)(Y)Z4#~h3x}3[Tf3L  '!?cL tp6&_(_:=rtuƒ1PrD$v”/&;!?hKM=Ȏ>$AG,W!+*"65el2Spkyls3%9$ D"4߽fm-B-%Ndo1s55Vnj m//HIGꑈCm?&~0b' i)۴C^|x>M ^M{싷f ̼c3 GupIⓄݎWbʊ߭zX#tֶhQʬ ҰE(5y%*H@!&oGFZWrF&U(TPPBV4wjxQ]K|(k D"@@^pJ , Y751f6 v:?YC0'cׂYjM@\R'p:^-3jo^|xɑ彡iwMu؝/9Ά؝buC:n^ױsoyɱ DS^dGCX~Z޵zVjMѻc(SwŒ:sO4 u+ggd0ȼs.nŇ<v v^מF(na%W:}*?j)k6ay?N<b@ʡ%;AT&;ߜ@;)-[U3P #6NqV SgI;"@S/@ʽleύ[Uyqv<~g{*V)ZRͻD3nf^|xᖰ\eUJoiڅcۜ_|K(#"pQl;pj%GU8Y"P( *)z irdl]=^I"5uF9pۼ2{c)>| >܍~Q1윒!h uƔ-vLi e n} ۆQ=X("2lwhfۿfmog?J9g0] $@P% b *M R|"|(E AC \ٻ۽$g5gf~{3L;bVl| УDtmEуʗ]BhAxɱ.YLX)eܵ|][ы-ӷǢܺb;caT2-J]3i_R\sggz ;o"Wxz Y"@@.pH}x/ov4%j **q3q Ž ˧$oJWdGkNZn ˦d$cϘə8EO]7j+lOX`CCaG/9v '"@ NCG?ϔa j9֙ Ḇ%]CrXR(dY/ȗu)5~ሞR0zt1)lh/kYBBe \q"Ef[ܵ7P&8g3,ݛ)Y =Fw~2rX+>q6X;;# m>v*_~d %ǚlGE'kzrHgª'yAX1_ %zx[N z͕z^~:v-D&xa !eP"^/vB^tyUEta(ͲR؝zД="@@4y1fL|n}f4L }B*6*qQ/Dؗ< `9HwlENӝ1H%ǁL%enDƆ8iR0÷.+_TD tw@N"%ǚlG횢Ep!Re-xrO=xOIͿˏY ( h6LNӶӸrWcq7j;ҟ`f'թM:e5 $iwIН"@u s^WzbfNq'2ˤ*$o'pWEݡZe(b>ƒ-RCjɛ)#Vdx駣Q#W9%CKQ/J@KQA9f8Qzs#D<}r=t;& kkǛ.ϼ\5yEpc,m\,IBVBisXs2daJ Ja\|އ<2LѨw3J4Ǹo&O9/5 U1;ո;b+X]+@B˅G~S4ԇg0n ;XVQ.CR H#A8ԯtD~!o {̗"eex;UϾ_ oW i-5Em ;D68 /ˎvx.HX-7&j5VD12H|\FʩHD"\ywyc•Ÿ4|株l4Y:HH`Ք&b,5Qm\"Ƣ=P]+vL^rX ~G[HiՀh%( DpS.A˅@'y _ Wp= qu k' Dj=N%_y\qC M^rDyU`Ȱ7n ö-<=âgݰS0+[|Q#5>9wh י{ʆ1BoѦTi8wf1`rj D  {vSׯ{>moO8](<ߙ՗Vv=!_AծǞqV մFz9>vFJ2V7T^rܯgzݶ mcm,I0t6jlG(Q݉x=ͼ'?]>7oz ژ&/ԭ, f'Hʼn|zQUm[ya&ϔ~Ҳo*u5A0mhr?hprFU3Gq#MQ|aP+LD^B*_"ɂqw?h&/9M؉8vnUӤzSVVRpI ;/$2ذ#FY٪H동KqR (]9̘k^ЄMHިaFi{oGuFa$_f TNu"@y6ϟGb<.zAZォXnȚA5X l w,Sҳzp@l6~XX ;)vdO8/9p O~Oi&!jy[Ra|}\8x1 $ O ܾ?j]*̼cQIq{q~')k55V7,*tu,M+Ya&rA# ;Y;HzPNJ\w{pK&]["u{ǎs1*̕; ĩ83+\+dPHDSNK5ٮiU W86j WX]{ʆ;۶DΜq6١uk*N?~(7;]e JuKwʩ@."@60x'DĞk*}dMx6j _'D8ͳ!jbdHB-k!,7v &ecZqz7/9v]fw@ZQ)E=vJ6/9NEJ(0r{G-MJ[2d^qYXB':cGwcvIOذe XݫHM mغq}͘F})(@IDATp.DC3m]#,o\0U|g3~+LPZɓ{prqp:MU%u BU9TDDnnyɱ{:jBPJ4lx4 -l]"CzMExp~w̨WYQ_޽nOrjDN"@cRo4#cεpDHH>VzZwzFiúx>&1N&h޾>\}Zu_ w#/r>'^FO>_jј1za%džxl_mS!a-cD1<faJ37M26 1=PT FOXgXqˣZh=q.)33׬ j,mn͛Ύ>@2x+Qn8eqF_dU SPgZoY\B*_ν;Dn>xɱIwiBb;*OQ'B.mPwœP?mRʢ٧=hiNk SXȒ"@rhZ.#1O~=mgeoer_dG@1;7z|>dndOwϋ2ŷ %+L9Kϼ,"@@^~̕x>}>2R[눭=e^kQ&t=|hKpVxI]#&ݗ s~׍q${䧐rQ5^傗*_^m IZyɱ&]*)9T }B59Fvyq MJӢ;ՃLȆ"@rigFpvh3}MéҬ^VƏ@>1b}õx11l"w^rGyxl׼3QL$C{ߟ=."&B:j千 5$duAlnkބ)Гo'xF)Vk~[2[)FVEg>hm/ &vk8K| g 91b̩_πvٸѽ3vgY;n7?1(O\.x s*ʗ9ɂ̫?KNA'Z{7 keg= F [q ,-PYWph?~PݳTYPKõK݁ʩ  DLQwWa}q|ߏ8l'$,#:igaJWߟe=p_0cnr6_Y!BRd?%>Hp^K-˖s؁-J~f^?/9g"@ ǧqqBWAnw2muRMlVby̲gVYp!T{ .P^傗|Q@%Ǎ9ڊ m0=Q:JQ8쬬 ~n2eQ}s?XbW[>P;Ob7Q95ND"vRnH:ٰX,a>|E1MoѬT$qh"#ml39|>naǗO ejo}_ݮ|l !''T, |mVc-.o?ALk"uvuYzR+8K~Q'^rl&(tx}y}^IF&7w Θp(̤?~,5n(A\!ISa[5L [>nj]n8;{F&<ˆc:!s8jG9‚ӕr!ϵ+r|IW'&OHqP[Xf2QJ2nHf} ׭luRx._7sC+ݬr*; Df|[,a֖p(u$=ʸOR鍴Vz߬0T L[z2\w=P&yv0eu bsg!FO]!q1J8SW"Q@ll.psTV7ֲYB\~uZ˳=ȑ!>^rY|E5q$լ83ļі $ oQ Oaq%*.{7݅gP8jmB~Θ)!uԎeJpE/9I7),w$1-o{%ƈ^-)E –nѳ^ٝU>>}':9d5|f,M8p|R<3ۣ*+u!_ʘ}Ǯ[p{֫<ߵq"@C2*l&e:# tPfO>eQ@tbwΜ(W@ʼ*,*u_\L7A79iu ^|x1Oc2 '7u2%Bg]1cyrfkN`yL<^i\-ƎzؚTq+3èn4478R.X{vD/k׎W"ū| c{g#< پԳ"6Nl>5uK7-M5787Kuh ޓ7ϊw*Fd D| 8A"]ڈX%BN&i0[DŰ-\J(_*&C |jH-Yr{MD_|>#*!2 f\b|)vM]Ѣfʗ, oohU*x  pc %'QS"P P*2H D @[kD"@ D"@ D"@/ /$"@ D"@ D"@܂)5P" D"@ D"@ DR|"Ir D"@ D"@ D--^%"@ D"@ D"@x 7/$"@ D"@ D"@܂)5P" D"@ D"@ DR|"Ir D"@ D"@ D-""@ D"@ *7,991'"@ nG+#ڼ/VCY_233ÿƆx/]Ѹ-<_ A(Xxp+ w5/9ď=z0g/7e UW{{0'8Ip;;37@2ސwYYzF >1|ʗ婾DOs'z&D"@Pk[u҄b~̈́V''VV-|z"M[[5T>Nڠ֪bC8ߎy=_9c^r:m ‚QR4  cUL3o퟾%l7n`=^rK'["@ )Z_Y]T]Áfcg{+YI^@@ՅPƩ䔀"je#Cj?)35x 4Wʩ;xɽ[D ULgogXH[Ili@|J< D<"[s"u~DZXU*uɔ٤Z!U[ iѠF̔c* aߪ.Ze(͟_FY5nK/9d"ɽk|<.^ TU/lKFaa&x1̳CIu磓jl/ձy3⊊075lo)LM-G2"@ pHgv御nlAEM/9gߟQ6ޝ{KȁBH!!~P[y߽#=oa;E=oPR;el2x[n[!h"oQRnտ̇=W4u*gghґׯ^ƸC%p(ES{sCX=q/GlK1Qlq>qHr!F*_"I7XNݶ?(iTR\ئ@T(.T9HO p6D4SDC=?~G&!\ZFB8;R}dzCeM B.QLW^re,AmfMq,KNv; CW m4 VriJoa'9Ჵۢ 6(Dwmf,>X)u6KBw"\}{#wD4;dZ6`ݬΘS<]e-uS#n9*_,2[p\XJi|W`܁~WC=/cWQp&΀G9 mbzjAz ZNݵ?(uj )r<7w AT1S\u 1zt ە},{+ʭ+.3&N/ۢ[9ϛlŻ>Gv_а&r- D2߇WÜ]7w !Q6F\Ž ˧$oJWdCּz˦d<'cϘə8dGDO]7j+lOX`CCaG /9v '"@ 'AT&4Gch۲&vCkYZ!]'l&>t/A^t)Uq֬P@K:"JpDL@)zv#F;e1t 6򻖥/$6ْR"?{yd(Lʳ͔b X?[{X+>q6X;;# m>v*_^S!rAAQɚ\5ҙ@@IZJkW6<+,䷈-Wѻ0y\魏˯2ZA\$-^EBCTȾ꠬]vCaUj%]X2J{v'74AOD"7$MrgſY,f5?Aƃ|rY4 ;'+5C߀BeJ\h| f-XN4ҝ)AQtgr7?ҁxxq *S/luFY.ϒaxx~g*K f[\^rt"@fy=Td60IũC?391Ehx,^o{=o?l FK&kazlKӟEf~DwbƑ&x_e3w/+wCɀ<~^xH+Ero9C$Br0+KmcC4)@LWo]V<|@'ܱ?n-`\WH.R=o[>y' 7>?.?FPd}f++2:MۮR(O]  ި=L6W炙T6,Z մ*VݽzP$Aw"@k5y _97뉙:a0z,C}i21EcȍCjɛ&i#Vdx駣Q#W#f2#_*Bs 5xp(G;x:Jag@Joa ވ > Alu%Euʤ^FQBisXs2daJ Ja\|އ<2LѨw3J4Ǹo&O9/5 U1;ո;b+X]+@B@vondSo?)ǿccSWV3T r7U + (BxȡAECv*[)f41(3/cŻޑb}jxSc[|g%3au9&=$ҖVmdB"k$_>nBʩ z D<$6ʻ#KߑbwguqsPU6,UZ$E cjJ1mǤ~d =DE{0ߵW0i8@+D+]Q#.śҪKQ xDqvKأ{t*F͹Uٶ(BsE;YuK;LdJmxU[Hx P6(R,yM ., r|\݃ʅO> ^GD[X;i Uq*B*_ܱ?:<CǸq0,f'myg&=3ؔA-be2;J">p]{јG.k|>r3x bV{MwA pfcx@="@@ʥeScZA\xi%357Z ^+`C>AծG,(>ι؈5}ۍBL=enh送3n=ֶD0ۣ![T Cn}0zzh,yѝ;|GӸG""zg1SfI>mq"_faUU |f8(0c*N<ʹulJFP.L>/mi+̣BDvoP#aݪJ噣֋zo;g^XBi~B̞r|$&Y+h&)}Qrk!s9zfD +8Zy7ВcRn`_rڥd6;H¿QUC]4|tfGPW7 ҌsA(S#Z2"@@ߢx$c0W45;.QLL7IE50}klkVmRBBJo!K放7@9"=sF%,˔;XcMSv1xdL6 n/5@WqcH"@=L1En;Oz¥÷.K[Jv + 'b| Jwe[niZ?+C>qda'uBq)lxTfv59_$aFű#y\i s%ca%v/o^)1{y+bSqppH3kB59T$O *qEE޼5gbr|툕;UYlmKK`C+_wuʅS6 D*ST?NxA.]TN%d"D%owB@-uBvL ۦtC w$SޘHãy DX,Ur-DƮҡ,{L!N%NR]v:k6l.HK35EN%ǩH3 @~J}-m|7Qqs}Cc;T2][ݍ%]>cÖ-b۸q+WWuF1R\0)4X}I$gλ*GX-iNG//gV,ӡB@v/^?gq _~ q;}r!F*_"ɧNA['.lV2ݿ# H [=opPgeA}i=û3*%cxVcw[JD yK@mc=&惐}ܵBӆu|L b_:Mм}?|꾨"eF_V吥}Oz"}R8Ԣ1c`K |,7>;F/BZ8/9L!pc=Ԗ{hx4(}]nHQw/ 8ecjM`(*#'3z8BBtQ-4$LkEQD~=9?} ^."t>^ 6Gf31Px8iulȣ\!_HCALki<0UХ *Kx'M\%V>@Lsu] rj Y"@@.4ùN_5;#1O~=mgeoer_dG@1;7z|LO*=Kv,S|PRΔ3#D #)&/*ʃ_Q4Ex^e:];=e^kQ&t=|hKshؤ.GyS8Q]` Zf\W%ʗpg} eQbUR8 ^ }B59Fvy%BzZmuiL ՃHȂ"@r4+wqXz8;4R5ٔ=@>GF ~L 'Q/9˟p2ЕWߟ9sfb/f@;!)2$8%lgeK}ƙL lšg^r<"r{luDMy!^<1i@: 'f 3V] Jl?Z:8K>$Ag2^EQe vQz#Kf͊řsL&Mr>jYL Wڽ.g/9Yeip?讌ăܕRHQG6g&2P9y DNȔP[L"5X |hG`EA;Hhsfm!-ʾ_3cV"Ofk,[yI4KC"c[DX$'zGO'2ӚH]wV t0Wq| OL9"PPl}_pMFYy7nf^;,R&:ߌ,5n(A\!!Sa[5L [>n]n8;{F&q"J{+^,}-椽k-M`J9T$ 9'4KQGWz^:GUT|-k%eVp"m\i"X3p%7$b nu^:Ch$ܕ+ݬrCC"@@>O&BLҳY[*JImh7m2STz#U2|QrVH@mP-\pg._(<;:f}`63(C4Cnc8p E*;|^YL]殯n8 eądKtyd#O_NHMVN4-<k޵0}LaϢ~~sg&"G[K7\,5F-<ŕuܸvAQ1jJ I8c.3%\Jy{gI˚OŊ#V0K^rw&_* @_ok˜&` 6k^rDANDR9/Vy ~^ {}g E؏؟\?9-^P@*Vf֓{%xMls.Gb:˯L"=$cw"2od \Xpalٺigg{{ \/S[9ͳ`Qղ"uruˣVkhR<3zw,rdɉ5h=|?f q*4I={Oꔫx*hɂ"@黲DLFɜlզ=; Y-i#9lY{mKyBy3qSG^&vk'M0q=#%G44_)Ob4(4_;goďk}J !=0쾐fMϷǶSq;>롴~6փ#rT`CxoǪM0>Ζ Z( p+@vi{gWS36n\%n_|d[ 㖆O0Uʀ0mőra39Td W9͍ /=AalL{Gq#òy/Em4M|jq醴f{zFY YTN- D!Exx4+"v k#bJ : w_ğ}l%GúsU*Q 7<=b#Ͷd5}P0wȬ[%0CE0/YЪT~7.?8;&,yHmD-Vp ]Xnq$LņDSis&VbNwa5|\\R>>HKKGB{ɬڧ1*TQ-=R|?<}~ &a!ҎͷgrUP>TEμ Vw:B)g[{iGksgv)OT"@\|) D"@# WXO`L~ٞaM2/9dx Pz"@ D b!D"@@r=N^r\O I N)GD"@pv7A D"@ D"@ D.H# !D"@ܑc"yqGF&"| D"P8 Ӫ ' D"@jFL"K> %'QS"P P*2H D @[kD"@ D"@ D"@/ /$"@ D"@ D"@܂)5P" D"@ D"@ DR|"Ir D"@ D"@ D--^%"@ D"@ D"@x 7/$"@ D"@ D"@܂)5P" D"@ D"@ DR|"Ir D"@ D"@ D-""@ D"@ *7,991'"@ nG+#ڼ/VCY_233ÿƆx/]Ѹ-<_ A(Xxp+ w5/9%\mƠ~mQ+,%1A3ɽ0۪;/9V;kf[L~>:D[iyp [Ň.Xɖ+y\ hh}_1=zv*h't|\(_.Sw೎euSV/6Py@)K"L2Bjl8 ]`ӝMWg+x/R\-_<3ȭ|LGYZ;P8FC 1u94\?.«LyTφ! *( +Ƭ(I|5XMSļcqEFMX:[74/b+[Fd D"@&ϼxyɾd<><6JS3w1 !ܨV#qXk USPO1wgv\`K+E*nJƭbO c31t|ˆ* }l^r\ɋ\5ѥ4*R]Z7¾?7:E7,`@IDAT/>a#kV1`zܚ2kP.h;h~N|X0S;q{E;/9RBN御nAEM/9gߟluC׾w،(h\}=wDRƭLdoE}muh),5Mз([ )Na_rrv:ʳ3OYHyW/bcܡXc+W=+qxrf35۱*w\*ʗH޳/Y@vdڟ˭ɓO< ہ ѫSR)ѶaޫG?K8k)!l"pH/d5+:QHcZ1arY(+̋/926Ц^-x}r6'لyz{L\=}=[,<4P;_#9%"_6 fuƌ#XݢX>ez:R/ƸLrdMe[r8Z\;Yoael**0V9[m2Xl\C}vA.!rt#簶kٗ, Τj R oDZ0k(cK8n!F/#Z֡ (Xh@n8oY xj}J\Ŷ\sgݷAÎ@BD"R|^a%j **q3q%Ѷ\KJUN7mp+W%e{^|xq9C`-"l'#隌ݫ>c!K#ivOuxWOIM#;Q yƭɉQhl۷Z ,&A8+ndt/|^6pa׎bK fʿ"_OQ$di=3|aF?ɁxNx ]e3E{]@-SP(/2"j%77)_d(Lʳ͔b X?[kX+>q6X)ʌ4۩|("2,~WyBş[#v e`X1K_YYO~ةD\YqkJo}t^~:v-D&i-¾&feP"^/vB^tyUEta(ͲR؝zД="@@4y1fL|n}f4L i,U1TQ z!¾DX'~=lENӝ1H@T^댲L7"\%\,;]PUAy eحq KNCO WA1v>Wfv-SPd6+SY߇~s&kazlK1~#(.v,Ų#MV-#5fJ4$_8W@뇒x& VR9cx/3(-!>Bb|/D+-[Th16Ide˓ʗp\dה'tnuԛZԨa EEVO6O9TkePJ}Vc.ë]6=o[>y' 7>?.?FPd}f++2:MۮR(O] '+FyG?HujhpYMka%I;xݝA@ yH9 FWzbfNq'2ˤ*; UQwhVϢdFQx'SFԅK0z$CXyuय़VD\,^rȌ~Q\ 1|u(G; F={p|>%: | gfaJ ޺5Uzp}#Dl |F74 yyaLioܑ[9Z2 ,|?EplL}xʊ~&*9[ưAae_AB9Tx:N+BVVn0Yf.f41(3/cŻޑb}jxS]ZHk.-mK!w@i]v }k)a5VN*$vA0?TNEt'D&;mƒ W /fJoDzp`@ui=#a2yq:b1VV֣6Fn]7#Urw@2"@lȕzwl8ei{0F,`;s]L-c;~ժN-Dk:AEZM9R<3 ^G*-48dr!C/+H7ʅBjWf)leOL5'})hpz0(m"kZ.Dʡ%,8wGr@}uqƨ{.~O[z# M؉8vnUӤx):;Moe%ı" {92ka'e߀ҕÌIv)8x_M؄ftƺGvqi_~ofNAEiƹHT!D| !rxY%=c *b `bw(UE Al>Vjd9ͤ}>dD=sM_&dJX ;޼ G;A 1mFcWQ8S"^2_o.0/9)! "@uA1V/9fL;X_<6*m5R0%xAS4UNlC(w"44*T]w{JlW-Zc絘w+U' MǿwXƒ=ˌ-eME5-&QIHDT/T ,mdGY3,ws=s˙qg|O{ys{}M#˛0i?B2b&>O9"789_ H"nZ*3ؼ+P2Z2-dh>D~W}fO #OzņEPU~ _ ue"WOݐȌR ZH:x!h"j/77z%NjA{vn;[.Ɇo t nw/z+R&iF9wlȂ7(F1zހ^푏`]ZZ1&|z<+CV|FiʑwW5\`1Ew㱗f@ǯ=+\ɍ'OSۓָ7ƚ*|!G/d5_(Ú[$;r"^|y+1K9F.Krk|U)U1eәumimY[мY/԰7.v ø0Pd9ҥs?$@$@N t Rҥ)܋w9*'!j>JixvhѤ!nOH@kР9o,VRw*raبL>uRy֥2#{K'Q(Oު2@B/9;HnR37UvalƵϴox8nr)3/3g-Y_lz )F=0B:tvB wZϱ?8E=0:@r^\rMХ-*N>'L߱LJ)|!47_>-[,t$ ae8#U| $$?q_kTWm._CK1N@2f['՛ O/9ţdƄbc"M$@7 ,c i OEՋp7|4ǟ7c^4t]⹞ʐhӼFL;\TupݍqK8F_kR&c+_%K ss<8TFv$Elo),hJWG\8&4jv#wPQ-1Wv=WrЕ ]HHreQ&`qK'3^N%NǕ4_1o  YzbAېy'Y+ǡafT;FwohߤKM<[IYr~7FaɎxu8uuj T3ɼw-_lz*c:|7/6= s2Z 34m+-RTo2oxRvΩ8q9bivO$@$iJЎTJ?TZVGU%i~f7)<'Ox MWfh~Yx}Exop\y;y#{ڣsYOx _>!'teU,|*v}'%]$z7nβ>?)˿-%Ksܼ&ף̙XE$|ۇ8bZS;5%73dqʠtJ󟩣'|nu#jNlX< 5W l7]|$*$9r#κu0B`0I^!b*~>wӋ4Szh4bo~!`գJZB^EOݢ57wt H3-gQfG?eu׍թb>` @.>ɮLߩ !> ivQ*ݰfe;4]1ydVU렾0(w~ =P2v1emV `r!4v1T:w-y7uWWNHesh%..1x^9GO/9,4]IpĽaaJL8oK7tmuB݅0@jcN$؅IWݠtqw.?RIK Q[78YŨ]~PkƃP|#tzǀ)m7fM\/F\F#ae8}Ytv 1JE4喻xc)aH.æKpq>vNGx8i u(G`N/i 蕿Rot}CK/i FdUH)ߤh7wŵ1cF-Xwu}D_>/RڵQvcqz4n$fmHvJE]P5;HcjtڰIfDk6-62/ 0i85EhNR^Y]Gѭ\Yf3+]#d(ʠ0b8+eJ|mK:|IK^KHUP%P?2^nԋ^rj-]Z9&+mr"O ˝#nG专OL.K= nf$-sz% z }u-I#f|xNa1qo:8\'bTV 1en%\cj.kGyGT@ҟ&E24T2- S/7ᬃ>ꪝ7 aҒ;W;R:Meu1{EG uKݕ<'ÄbKyE-!}ba1QF<6(YJԄPr934{ܤEh=x!.^4d/ąx;q恲.xSt  E> r9;{ چ^ؑsb U)A|;9 7zDno14}< _\-ϔ{Fh 'lT]Gj4]nF&$K:MyZ/1.=|O|=P/9k$@^y=A=cgo&3\=U{EvI;Nf%9qf8t]i~ǂ1r'&+]PyJzVc|UͲ@a% J2B6%]=P!VsK9_厔 owqz\؍y 0jy3+ ZͲ OĄ;6Ǡ| g{khĻZJ8|JY4_.f.Tݎ_S 8eS.(߮;:4PhQD,0Sq!\ >\IԵnV%F$d##vـ_I=+T( é2p" yrzRbvEUQX1DFb2!BzO_W{(mƪ'xI$@Э́#Cǡ}(,hүE%-h{^^]=;oQQHM;bKbȳtW/9ɂwǚ!(m/Kgu,Q7@_7 :TbrCNn Dj!ШF)eTd]=oFέ73pX\$rQ8wh;zq@mر o& @Ww oki-M{^r|ӚH"us=oHHHn1%   $`n| z ^J G+=SHHHB ߡ$ .h# "KN(2N$p 0'IHHH P`ΟcHHHn")02 @525o!|O+?b&HHHB7 @%HHHHHHHHHH"N"I9$@$@$@$@$@$@$@$@$@$@!Ax THHHHHHHHHH@/4|ErHHHHHHHHHHB ! ^h֋$ C1P           I!          4|c$@$@$@$@$@$@$@$@$@$@z[/C$@$@$@$@$@$@$@$@$@$h@%HHHHHHHHHH"@÷^$)HHHHHHHHHH $J EoHR @H;$          Ћ z          @wH<*A$@$@$@$@$@$@$@$@$@$"I9$@$@$@$@$@$@$@$@$@$@!Ax THHHHHHHHHH@/4|ErHHHHHHHHHHB ! ^h֋$ C1P           I!          4|c$@$@$@$@$@$@$@$@$@$@z[/C$@$@$@$@$@$@$@$@$@$h@%HHHHHHHHHH"@÷^$)HHHHHHHHHH $J EoHR @H;$          Ћ z          @wH<*A$@$@$@$@$@$@$@$@$@$"I9$@$@$@$@$@$@$@$@$@$@!Ax THHHHHHHHHH@/4|ErHHHHHHHHHHB ! ^h֋$ C1P           I!          4|c$@$@$@$@$@$@$@$@$@$@z[/C$@$@$@$@$@$@$@$@$@$h@%HHHHHHHHHH"@÷^$)HHHHHHHHHH $J EoHR @H0T#f%`&5/jMdrr2Kprw  @7|Bg^XD[`v Ӆ nz1/}@?@wbH3!3DZb}%*[{-^C⛵Gk\2(VPUtle>D/9>GKkr  1mCmj0_}5Rᕆg:{/}ϡWy'ʸӷDM^r^5Ñj6Gh2Er~wa)+>U fbݟjqnJ@q뛓@0a;ǁhV[x 'QޜnKw](e͐o5eYqGP?_p8I’;ʸI1\9z j/ETq_b*H7R9YFe ]xk;ۦt^Ͻ1&  3Y4xD=ЀUMJFWܛSRenь.(-ÚRwb/ 1, bHs)yMn~~ҳH9}\nPaDhdw!av2;`ϠnxFuw2׃ }XGdtܛ]i#+ł}eB3wf(Z0gӥ>j!el tn_0X_qReNi{6"9qw[ܛtmF<,Nӵ~yc$m6\*Rťj$@$@$@dknvZ s;Y8-W}x{s6_Xi CwfNa%X].*D]ƚ)ee$)Mt$ی O!5D~=Fk6:̑ks+ @@:ǛmVIF^|;U5Cq(f$>In}|9E KN@{IK8~xGhoz/_(dwYMs^_9PM[?T|.ús1jzom}JW.Ljj4G[g`Ÿk9SX]T_K8y>y ߟb宍C26@:͖.mcH'q0F:&1{3g/|1AlX7e\aL$ř}7nDE:z>epfsoB_)UgĂ5}7|!G|2O_~,xEɣbrRmyII!+٭XEF, zS18F8: scסXiɧ9Q0 ^-CTt]wn7mY}} \#,+ vC~Ęp4]ߎ8.*hҵ!ӌ eF=Ć`*㣴9U8fh[~#b@(³:cŮ⛗LK37؜Hw9)>'HC>83Jg`H:bah]v>SzqK]_}M#+,}q&~jկ4H/# ~ZJҚ♊ Xq_>Ev&aV)X'#P9zjKŞ|Mȸ3n8LX_qm'm5y)/eN| 09W|l׳rm;>\<5l0.+%-f_TKy\YN!YvbjDp}Mj?yU.ǔխm}v#l_':a~0y=x 7l5ݳٴ>+^_=B/|шDe>}彗n5I{b \2zϏpo3vӱfA|*wc9")u߻Gve''@ĕ=OzFvA~f&c("ښY~^"X3EAQr'4yeX  @j6_V58˽֪R˾hfߘ/3" UM*Q\ECD 5-q#JK(% E8SAhÉ)cE˜(ˊ,瘮jˋfc'lƣC FW@B>bJv6K z֑*SFoUuXSk: .ЬHJw kwH9׃Fz?w`T8 h(QNY ]U7@rкYUe(ybj1ҲX@*+4%Oz('Hkt*fl[!eYp!HWcj oԸ-ĆlGVLzL؂lʭ91wy)gl,S:,+Pa #sDtm_J&_8{ew7.`Y{\0ĜI=ECtWƃÆ#Po[v|1`۷+G`WvOɈUA~1f)1ȩ`$~=yc4ּx-i/77(e^ԝnn=.Sum?K~QbKrW=<L4`$%q(%M͓pjX`g,YHHt1#(N=XjyHH w(-7.F'"By)ԈJ BEP %ڍ mdUB>BE#P]!~ev-Yn+ 36Mgͩ8q0GhXR4 !I0ztځ5a&-wBVN߰#p^,Jg!\>+,aqiS WFƧϓc<&4"jv|t]!T6T*%wl&ȯ?Foe̫'q\:" OQYCהs)C G6zK~)k;@}Cjg 6Ws ^7t(],b[F_ohp:qי TNі0,Hx "MWna%E˴WaV}s0wrP/9~,r2z"=zYcP,6ĖA1I[8׮RҕA B:djTIZ_-SD[Mby?UGɏ@ˉc|3JU^*nkt;P`Q,Y4cг~`꡾?GguٚˆkQ,aU|ʉ_YU?̘flQ @nڲnҴjFX新!vφ*X3Ҵt{]9uS*0~]Ǻw,)HbЎUPihp ]ccS QЭ$EIϾD$lh 8<'+~֞.:HZ̽ҦBg<#sX#rŏ$|0D*EhKqDLiĵ|Zvmु5X =Aݚ)AфHFkbΥ"Sh;+@&ȝx*frmRIX ?gfwerqQac$%eȤT!Pҹ+(wfl'֔ۊgAި.bfwcДZA;^GM˹^_|/KKƩ:byI37Qʠb{H6a[N|NiF>׻S@ YOoj(k"<%+c ,Zªyx[Y۩|9 VZtԨ_,]S ^ "&"~Ym*~s j\ C-TX\]rOԛWpe1[{[asĽr_'= ] oʸ \jLV*f ~\!}@IDATzM&OcDɯ1>PZtEp ,qbmKicOhcۀvƚN||V[@=&TIue퐌OwC1|1C_~kAX锣),G:܏xf(\QF_YZЎKZ7ya8͒OWD!9v:uEGНSbKczA@ &F6n|F[<%kJvrHƕQ6 6"kc vQ^9EJ0dȺD[5nYy[\TC6L[w}fbc evԠV%;!@qĂv꽖~xjB _h4B[Ctj~J/DD\\@cwI_DS:jJIp/$za87 G$ݠm ßĜ`ZVX].rozO8)ޑcY[3ZJˍ힂cPlaѦ1l߷Wa>?$@$@7l)p]ŹovsѲk8$ gգH,Z6'J9ϭ[}˹ ^4B1? 0 sgWex[sv6}}Q|twoav}6jscM>ckt5ՏxM1z p51 Fo\]ik*=uz)b_aIrGi})q[°`i{ݥxx@nx샵>FnM=I6jnj4G=mq]-q[\ H))ތhVOؓ.Iux`$:M?8~ypGF(˒9G3'Ɔl#)6{,óowׁ:z6/ 5y.fHJ(fAg_$C>8ϐy=?Q زpC͸oN>*6e{:6pO3HD[}`,7X9_2I~߰/#!xeiPE 4fIZN-xӚ7֐9OuߕryET9cVCUQɺԉMم? fP)uuɩzkLٸ>=q8Y )k5dGhӚz&DEIѠu` 4".#2#ZҘTHHFZlbׯj7!oC&uN؏lA3kAj),CZm5Pu=ߠ%c1͍;;z.=}n<kw0}tNlո#|ĵ]5aZ6:0XyzWi|\94z8{ ĈOFeNQӯ^r$aIXgl0e1ZM>b+TO'ggXZDWDSĮS0,œg(?}DSs UTa1qߥ\5zO}i86Jґ]7{M4šS4._/aRHf{&ft?Mtv `+ 5?ZKIJ4\/O52źS_{/z5gon7X4m!sն 0or̪YTGcR"F#aT{eX=@^r2nzņ1V, sXA>nu YIX2ڛ*i~!:#{?/Mz*MzjDfb1i %g*lGUF"YjOn[ScJ;5 :IB#X9_2z]SR9|d,x I1ހ^푏`]YEc7˗`FNmb%eozwރ`=s%]* 5heQK˯r"͇:U Ď隸)_; ?-4 H 4ǝ{㍅!ѰR=t:{ۿ9R,&NeZ/Rݜ :=T9_W텴1+:d\f<:|'&cѱ2`F8 Nϴ7*އ.iٯɧnsUST]țS`܋K1[&TK% AT݇P>|[̧nБHH (57IR=6FnB)NmV.<(c}׻$`ʋ}@_sY`N>*-%FpV('4b {Y sO@Gy#l)e)lWG4%}Vo옏#5֭=^r|c/ F@V>NIו+p@: 6`EU'-rwcܒ&%%!IZx#nz2Y7Ô]ckY-WsƇr3>;Azar<'wu~pی.!u^mBq7jn"PmBʱܑukH< 0Rfؾ]XdK sl{=d9 }HHrW÷q8:Zߺ;M,i:?h:OJi4Ttwxr3GEF<"$׫[?Q輣(Sǰ-g]sԳ(8~ʘNeZfƽS0iNwoc sCa\HWkC? Gy4XS>`zɑ5'6 ؛LO~.O:(j6oo:*yѯ[k!v_~k=W;uf47789{Zd/.sN:!.Z6 꽚.r3;E gLhv[9),yp3(Q/ϰ?S)>y˧IR^$Ξgx=ۧ;gUMj(^~~[췞L^0mVO/0zC/  d[3)Gd*;'l"\3Q :FvHSO:WvJj1ibw0)Q3bn&qӨ"<=Gg9wcWW5:ϗ6LE?atO~ 砆\j){FYq|ܽ s !{(n) U!.*? dƍʒO׷nX/9>)6Qᚌڧ[ W9û~6`>zTUU Z/9.Iuks@"79 -~~FrzmB2˒xޥrb~Gz $ˆDkVOq C'$pX %!Wg_W*O)*sq~TS:OOBRwl{`&Fq?pg.HH/V'pxP3a*=kq_L꧟;nga+zN#+2G'N@Yd0Z=k8ѨPSǎ^T-ޛ;K8 ML>FPv\)<M|;x}v>?lp(gN|>ݿ~uev!cqJLfkGSub;$ν$1|M]Sy=J)Qg^l{˂0}&CZkF%'RUԬZ{>ž0]lWJ:`b0cYrSAB|Di;L(ZF1ZtÚnFc$R/@% Cb:#ae8}YGWGi胑c)a)æKpq>vNGx8i u(fG`NS,޽ [ž"&5)=ns73^ټ~ΣcУbRsW3s&_2kArO_<+vojc͒x oNtCJΔ&el&[.܅b;v"q.N ХsS fqz4nocr="y<:%]zOlDZ)&F1 Z슀[zϻ[fVxޥ4jG$@$ -Z84q.%]"37wZ\S< +5$ ;z#U.Uޟ/~j|~@w9FꨂIFwvZY} kZwVb4cBA(V /zD=7\߽/r4heo)QOUUw6ǣAx(i*? ]+) Uk %7>oZql'VʟPR|]aɵtQPs2sq&H &~(E ɞ.Q|Pw*p3#ixi-)(g[FzIo}<闳 1enoǸe_:cSJux /u{ fQG-!ʘ{8_֮٭\͈r(TB:rt]5'~՟DOosQ#|Ȑn[MW_ǤGNyb:8\'b/S>fBtx4_V@u.0`A[8.4,a(Q5-C@!XQq`B8@YG|!>?S @$v_~ ?%!fZ1'OUrp7b3MOiyG=cW08zfw (;*SR0K_j)U-#[19=^=UT#V/vl%I5d*dsv>]0c♳oL|32RDu Kj(l+llrw{8wЬDπtz1U݄W27+zJ#Ŧr{|[ .x8pr#H HL^YQ z ;rNlN˅vr&ntmu3z*<ނ4Wdzڃo'G3Ӷ1|+(=Evw,#g$/Dcilx:[0Qi ޫg~l)JݹܖkW59N+:bJ\Ɨ|־K|!tmNW]>{3>pVM zU?g#x-O[k <*<>I=ڧBSiYruvKb@$@$r qqqʰԋo4zwo%')+(T( bYRy%xME@ ]ǮaYlvw&]mC1=C$8]`u6ze?Wz݁+W]I2 ;_oa_&Ÿ$/ 8RL +s"\ڎ_=5+HV]/Uq5Ct\毦dw^1thRD(HS _Y^{7`|P+XѢ22˜'ao è#oKҴkqgbٳ(\-IHgbߑ5C\[Q<"/Kgut i.cPVZe, [H]Ŀ{6f]S꽅$z>{s~jP&bc m Mkdtfkbȫgu5FWS=lW[Cdd,B<=/~]SIRTK$@$K6|^9MV樝K 8@S1*i{9qF@>˛ C@mfvZc֗K\ C7毛3$@$@$@$pcɍ-Gc5#,-f[~{I9ޒ2 !C`)HH Xq^rׄH `ϔ)"   P!$zgb݆Y~8}X_$\4#bUTQ ekJsGE"}6ԌHHHHHHHHH;-}d2t,hTRA0z[*HH kHYTrW¥h    ܴKLڼfz|̫8pxؙy܃|$@$pHIOY=3Ӏ̔k8e0DB/9EΛH `#   !pnn߬=^ %c Aῃ;1oO`79ܴ '        U fbݟي8{=z GٜmX) пhjf8p+OސA|g^Ƿ}{GPVs{kƅ%ҭ^r QxF,"_K#ƊcK{t%ȏrs?{Dgy45ͭ^^r*IK@|jatUxRV1=xa/,3p`ތKeVrp/9_8ngS 6H]+wJ'"l]GXgI&  pOjf@V2EQ(* )^kl#S2jZ\ caR5 Ͽs5ҝ`5KZ_0gpwmFoIfx;m{zKy/*Z\iFo[y"zw^p*6Mk<'^FR# f{w.ơ##8q.Ū5ǝ-90Z\_w;D/9 Mz~7#F.;-Q9VY̜ˉ(3bfꎲŒk;8{JmZV.%eĶƻ4w*?z|7ieƍF1WG#ΎjckM#ܔcesQ^' /d>C`nho9HHHg>`%J.})_bʆ(.[N˴] O8ی%#0qnA|i z̥ xQa)%'n7̙d9~ \Lh5;{'T% Zz%QBHn^ u*ĝm'kױZU)ޤ' xI$ H24FXV+Z:I׾vm XS(HN"Pxʼ@ };ԏީAl؞WuZd)^M6mA )d0aavZ3,'Fq!i&F\ \$@$@$@ 4B[4FkcaУl<.ۂ7߂55RK#Ws4Fo)O .ֺJS%G؈Nw/1zb_;QFf-4^rtI"*^yз-P¨Q~*.$@ߍqOVK-%VENZ.W(_K|ja/&*U!ZUϾ5S~ӤEj_rT M:_4kpCbx @NPjbH2qa-Q3ů~Ý *VW4q{.\L`͑NA2baX{rS"=ы^rMG.wc3~RaA5jyz#J/A#T=p޼- /72^ͯRwРWy"pVv*g`Ÿ&]C4/_ㇰ|ր7y>n^g;*m<$FޛuJٽ+]:ʲ;/daIo"K*i(fq凣n_T|9s↶bWRK8{"l;'I&OHH@>7|= )׶KConzW GEh>_#5mˢevQDs%Ge#(D~|o@k%'u}ڽ*u߻G\v=K[t$ȗ|n<4XU>v,q7JT/9rjm|߫:²`O"!)VU:6!cuOoѪJz>f\R]W1De܌P{CMcJwA>|;T0TUNˊ+!z lM'4p]k#2Rocɧ> 0PYCJFŀ P6y{06/fnD&-i!(>M⧩Q$Ӏ}xE/,0}4(*gB^ȕ.X  @mئ"E]X,xVaKOE](l$p%nErp^zKNpn11ccd_ϑzQOյm*JxPx/QЋH KIr[cFU%GGYDqDEǸVA{»mO-PR@FS.(U X+ܷ (r (@ PFBKG|I6I6iII˻Z;;owvv޹ r"3- a #NZe=Ew;(<:fpq\0;/97 @h|[a-α VG¿X?DD7cJL"=U v[(IPh/O kqt|4ELքMe0Ҫ~eC^@7]9PXbcl8j7^ Vֽ_#nf##ABø_P=dY0cTFS}rs|5N. Jj0BBD"$Kr>&"U]>Fk8w|5 %+G+-Ky9GS lGpaZa܂CˉˁѤTiR\ҁpx86w,oi/%<{s:4ǿzۙ" ڈx4o6&K=}D?F:jW0u*'w.k*OyXE hֿFMkĦ*P-*]ϼK3KGHJA,H9s /Dn`J౐PC4 R7=l_2I}b^9h>%Hć++!_D ΀vZFT}&FH#ST^n7cϴDZ7:+2.EE"ٮ ߃JdFoy \ >%n!-¦WuJ}o墆~l H]y0+#j.s9-kq$Iƭz7*EK M@œ߱`Cd0UW F nV#ӫe̊[uU~E ̶?:Т.l3t4 /a+ _A\h\EU!|UmQG:"P v# ݬ ڵcg z2T5K5qKg,m,Ua#j B#~wc1kidw-ScPh`*$ƼY{C2ɋXIqݑ=W%UwLP/9MΞO<-l _}^1Ф$aP?j XYE-BCK+7!x?j>%}dFEu<>+yoJ̜~VjOBl=)6X+wdXu.R_Zṗg&ô%l[wY#{1?>97HBZ{`D]tb_~Ak($"@s܌&l9WSXӗ&a&j>~ >5{W]v$VLl& ,͌*f{ O߉dRcT y^g遗:9RFS~\׫K89"P( Pҡe囸8EfzJS:^)/1^<̨-|޾7wv}Fo! ȂӠlciNpTnr X Β7:rߍ|mϡyHCVEf'gܰ] Yc)g S=V"@_Or"V/{u@z0HLڊ5եU3EV G?^0æI /o2z n^U pL{bme %a((fVZ^Ht961zDL2oO7A+ hJxu,0xiBO{C/ ׶>~>'Ż]RvpfytBR5X6zuYӷV0m;>0ǫ#~Ģ/bDG۞Coʾ5twM9$l vb@PIߡ+>@ iUk;ѷk<><+[LIVƘ{ÿμ7- sCPJWliM4%0uXV eV1hՍA7LqmZe 'ݭ+VDʧ&C Lρ[' ]}dž< Küdw\-zud2/ v}C9IA/0-<#n<Z cK;=ڗ{eSAxDkA\L k$>qmKwfEC%V1}´xtǫ<#=u@II+"inCɛލ?3ܰNAFU(5ʣa8ؤ9uyh`:+_hxsmf+72E |!::dwM'Sm!Lz]0NEXҳc[t(N7+d # SqɓR]&kPh,Qhh]^>yE=͵R}ʧ&C H{z|m;GFoql,9Gٷ.k(ll' |Isy="qA!.Kǥ""@ $ Kc89Y,my򔗎e=gx9l{l'k54OhQ wL=Ŵ5#C5+_(Pwħy ^:ܾxs; +멨N}5w xa/{å" ilmiQXg!/L଼"/Ƒ:g-V[qؐAF5Q¨tqVHȁ"@g8o;`ʧyD"/ 14t5Nj2{ٱc´1R2N*if{*/3gNøמs6]99J}gB-Φ˖{܀6&c3YOcy1/%mr#Dpq](KzV/\wRZF`װ8\v]wS0/k Zfl?/+LΘK9h*E \Mڅh'|HRy=N|Io~/xXoqЩ5Vc+8Yw{[^`[0uՎ_]R$<ұ :A@#뽡Hڬ-0{V_+^‚±R ~ WyK*n:rqkf >~nHsnO\=}ZHJJJroa:/&z]d|/ipGdAۯ>PUQUCKm@IDATF<JG-/J儽(XW6ecYkI µڦqO,1g9-lOKTUsV^?,/ʧz4 D<$7#5` @fNEP" [Mtjd YjJVNxjDF}fPu\:s'Ο[>(4r94l7Uҭ21y5^ Gao[ -7u:>. \C=MmPy.0Ń/AK\MWt3q7A)Ǿ):n+:6moƛtouEݕ0jHttCKNw>dԮvBš m]_eJy `T~jb KӂhIL~)0XzJUaP`) ]wFraT,u8gZ|Rُ; gPE+ f;/ʧ=_"@x2|X %e=#Z!͜52v`vPj MStV=zJҨiLl3i:37|>\ȋ/;Q5r,]:JurS#|*V\lQy(;. FEg᯷s"y8 Zry   ;Ix]]Y&0XڹӺ)e3UґEϮAU؟ڝ?۔OJ5T fRw!CiB~< Rv8+/{P2'GBuČ^UMQ 3qTz[޿xNHxߎʺOP9JW@ ,dZLt DawLi'#Xq^)/4x-1l1-pKb8{#ݬ_)̫bl MeT`x]'N0]xc率M cIoӲULf; l8C)^m|jBA;D""##1Nxi}DUX ܺro ܨuUF0 ?|2|V-ͶQ}ل.X{1kt$Zr >NETFBɒN^=?}s(,E"ztH@!&FnK (Eq.Ӱ `]S^:v#v~d=alS7gR(8Z$%KBfQZJ0O @ Y0x,$7@HXnݏ>[R-*T.&D (D"@p`- ;Y?;mkұ!._"@ D ? g` D"@Nq?&@ _R D"--wA D"@ D"@ \ F!D"@e"yx##x(=;@"@ Dp`.T"@  hYzU9* >د /K@'@bJ  D"@-n"A D"@ D"@ T'H D"@ D"@ Dx2|{mH"@ D"@ D"@/dEt D"@ D"@ D++nE"@ D"@ D"@x 7/C D"@ D"@ ^A ^q(D"@ D"@ D"y$"@ D"@ D"@ E"@ D"@@a$-)KIIArrraL%"@(ㅧjl޹}D?]#ռ#Z ^rX|WXky D;T:;OFa~ :,GA j"C;+k4WibO<;GP>\c^:Gux uD04|)^}OyIy(;z[Lv:W{~= o8/eur%D0Uɍ5_ f9zpOKjGJ=mKY}T0a_S̉v#>S<^/y M@Yzj ͽi9!'ӟ^ U?`~Z*1 D"L^IxEA 1&0|PkX$BAUmR*ZCWcL.DVS:9a(@[%TirNCN{%}?sm )P/xu{h*6 Rp22n_:hG+7X|C[ IlyX~-"@ 6|F,fmahgj U%U|v>kvcF\ dFq(g4U1p=\׸I>,t8$L84^[i*TjjclMc^߹fYұuXcxvr^ gq\ZVLPFobBo&^:ĝ"P p{op(O$9Ra6|;t/Rooy^<7>T2y"@ Daނ 5؊FoCx0k -K -H[5;W:Xk -@vH{L=2Ë/.S\˿|+LB1cǿl8jЗF%QLe'NY+Pe;Rh>Jұ"P xjyjIUogkx!4*&DQko B,oţu)_xs)]@f\)u5CZ0#q6ӦFG`bzN=XEVN 9I`vˢg%D"@lp]c-Uh[Is|$lX}Ogk$?8-xWi1.0 Tkx͋/ lQ]U}F>rCZ*bsǼk.#766G_E_^:tD@a&+fuX|_.;0xҍi$2%P9fGEo.t5-RëgaŇw駻1LJi51hxF7d{y  WS%lt+>%}8fVLc˭}[<%z٥`iڽT40 D#{ v?>`95;9}q6aa\4_l70 hfU>/>t%,ѡ-x}p4'y\/#N\:~[7<)Q'G"@ %ΗxX7=ٿ2z7|{>8|r}>; GŰuJ:"XְɌq`nziN^:rqӿEL(cc2v~Ƽ0)x]֤9څW]3Kkc8%T6ӿW,{ foO7A0o=='d.hqɌsfSZ;+>_J [S\[/[2=7WU/|He,B v_Ct!bZY~ɛ=ϑwmrTf"9u9Vt]ngIG-NnQ9h΃ D H Jp8Lo%~pjlKVVZB*GS lGpa^jZrmL8MJ.X A{aC|_}~-:㥄gOx4Pg7Y/v9tcGGDfJxyi3#="_>æ)S0]ƁT4A_5bSHt.g^%뇙Z#$ڠUF 9o"70%CBXH(J!GIw)|a!<=.S wrس} $K}ucK*Zvu R1,RЯ8^>oaJan93-ѺVlJ VF*k%_>s{W-3kāQzcg#-[4*N٢wR\#Y'ޑamu˜TVm?W]NڨhIqMA"@@~P0w,Xx~ALkCU@(_ROchz> &9MVS.*ң#pJCKѯ _U*>h*dQG:"P zآHyjZ;k>5`~lAK'G#~Vk5{KgYN#xljC=ēfUMe1iqUR{ '|=.WMΞO<-l _}^1Ф$aP?j XY ByPrIk貆bl4P:`ԩA,73 NzRlVF>\|1^-ĵs/MiK⼷0FiKq\b뇛?FH~ D oa(rد౦/aMLԐ& ~5{W]v$VLl&?{ &6Xyxᅳ}08~,ƌxlOOz7/>f9i`kӺX@~ֺO}5V5Ab|-aQ$:4,Vq4UNh^ء|j D  (׼<bL[X=]k u"KZ/!h=htk}}Cvjt¿wrP\8J ް±gvLU͌ޓ[I!/g´뛰d# FJG \?Չw`X#r3⟱XxzK@'vyjKDl =M<̷_aUMa-W93uN?? Ռx iطeչ~-n.&ވ),zdy Kxz"]tXs*'jF/9ZL52UBNcbա%, LäNT|a8 'h!)CѦٖ-ϸu̦v&0k0\a"!hO#U"v\ ]^o CZp,_o@C(Tz OX"@xm>D3n bMc|oQPx"^p>lp=P >o^fB"F q@̴zҋ0vdNypgLcuyT9%4(#ԨW'–gZMULȁBK WS%\t؜Ɵe4q iCO<6joZוM"_bTHe tЊS |Nج հVBP70~i5fC!Gȉww|$RwaƒL薆=>esyҽ|!麧CK"Y\h.MI:=2 ;ZVi ^Q&Oa4-<f y&Q(EJꀪػ}zKI][޷TY|w`|$B nl!w:U%*^vخ0|.&BJZƉ߱AK?̙0 r6]99Jg!KrKfgeK=n@SOwe3dšc^:F(78pFpLxk7{M&^m2\d a#aw=-\S -ZjF!pp8_x9uy ^:V(Y!y\prIh_m'T1%3pL' an34͗jG\0 :c&}VX>*?XdW gQ\~xǏy@4OD"@\' Y0_-[0KFaw}Ve*R`YB;ի˥;L/lzul]8tdo`KϢ]y=v9ǖWҕ[4$8[[QKG)Ws?Fk ^F¨~MD yXMD^S^:;&)e5T{_UԈ+3y Q;aۍ;:ڦ?s i!,*)SkD[y 9$a~om yZѡ%'SڲRYG Eǵퟚ[JqsGGWOߐ\,=;!w p+z|Gv*4|\ R |!g9|ty۾;Bwt(IzvG=SϦ[KӂhIL~)0XzJUaP`) ]wFraT,u8gZ|Rُ; fB .GXyvl0+ܹeU~Q>5"@x2|X %e=Y!͜52v`vPj MStV=X/7@ʼ-j Θ^u&:^g܁ yc'S\G_Nn~xcOŊ#^#/%}gҥA(,VcnZ$AK8/8ұ"Dpa*1,Oy7H^Lh&/}ׯAU؟rxwXgGoS>i꺎*ռ_PET+ ;H3a o'wb=> 1u|l=qWp=_u_$=yz'?:ZT걪t Bģhj贔[G=?8 퇬:;0U[v k?eޥ<֦^2S>BKD"lwߕEBa>4Jvnּs&[hCVJڸRw6ΌE_mX M}  Ga+y3;IT-avRv KG5Դrli{K2k뉿Y7퍣~lm!/cB>(,x7xtG -i姼j?1l1-p+^hM’{qFYRWc`tUv/x>{C.WӲ2LjMDVfh&NecfL; kѡD3n<)X84J-xOZ?ҟy/7#6KZ=֩<Ū8a"Zx4]4ͽպYpd|RKԄv D!PEFFJc!Q=LT%A+gi0[ɍQZe . P'ۉ'~ilKKM肊OG!WS,[ۛѺn*, 4 cw=¢o\t,GDb B D%/~t܏ )GWủ""@ Dx 2|{˝x"@ D"@ D"@d悑D Do$kH^:ȈD6_PD"@(% g(UD"@#D -+Z^U H*KDž"P P*H D @[zmH"@ D"@ D"@/4 /C D"@ D"@ ^A ^q(D"@ D"@ D"y$"@ D"@ D"@ d@ D"@ D"@ D^͋$"@ D"@ D"@W ÷W D"@ D"@ D"@o^$I"@ D"@ D"@+bA D"@ D"P jy`}RRR\SIi"D"u ;xᩚ(Avv6w.`/3.`E5ODXPd,ܽ~vq5/,;tZQ(W6ӢNǵWpjK:pt<"P(z.QQ֗#"$$ $<啿x̫®a[r=^Twb+T<1#D2OSxST*yNW26 Sw5,7_U|??'S9lɆ D#t.72Clxpd9:Zm;Я/2Z3w&[``:m_i0Q;GP>\c^:Gux uD04|)^}OyIy(;z[Lv:W{~= o8/eur%D>]Fo>Esm:kugn%Ng ]h)ޙ_QplSy m&oŧ]raJþXW~x_^ػ pے]vi BS30f!n~5Tc"@ D@XƃbM`5x -ұHp) y7:N[;LƖ]Qю20a^rE:D]#\өg07P7F Ru1`:֬y8y;sQ-3zOOPt%yX+  !r0f&6z ůXU+c&<嘿x궏bOaY=\KOEzǀ,x_ޜb̷2z?})VAAy,mx[:qA(D"@Æܨ # -,Z GUO[ҵVBS1T9ϸp]@okrFcN]/P‡!ॣD/8{2nPa[4U}6OOy =ґk:1<;w셳8y.LCj-dz(W{aq6A!r?a\r2rp6'_ P?4He6W廙/;q*L] g;R本h>Jұ"P pz,3fF:5ƭմT+j0y(xOQ?H4zgxh؞ Q![t,v>FumcVy퓰x5j=5 /Ra};]kwхw$y8 $*01W#"@ 3CG۳ Xa̖0/6,>+䥰Ur_׬XftA9 UhIEAphs*!Ip/o_YD@"Z.U^IƮN-з([׏aʟ]rWi>hP[$Իr,'u)>"~婽ścLSrSw<3,;ط`)eNe-Xp7_CK$]t|<O}gL.?ƎXdwy(# @^C e `:|nK÷᳾'ۻwT [$#e иfz7Lfh'/yJ5QƸIe"ya!R(*O__CUX eXw)ӡBaOƃaրYl*߿+3çMqϷ~ܨ)%D'ԁśoZyk"h51M5>wgw,Ɓyy[~r|+#_mЎ~=o:x2Cl؍x},ޒW#f. vvY7&u׈Yte{лРmi6Tf")mvwf\V_߇%s@IDATxɟg"<@IOBں%GS-;ǺS,RЯ]s{W-3kāQzcg#-[4*N٢wR\OT: ks5.j.rZFEH[7Ey/f8"@򑀲9# T^V]1t1xYT#ӫ~J*b?aE Bl}]hQ6#D`=/=:?"z x80 ;}U4PM^eP;Fo:iGC^@QVe(;>`֓ '_C]:cI\opgG#廣Za#j jw8~6Fv25ǬOb̛Uך<:O7 I^ĊN4*kerWtn? Qa(#$'c\w$۝=yb[b II 6;=0/Сȝ~x_xKR݃пYnfSi?+sg!uy,{;Q:_ iW 3ya𭻬q<\ZۡNyn-=0.:r ^? a5?OEK MxJ;)a=DŽ- [k$D SSwߕ AmGbŤf2>譂h6@nxmN%뗺:OpZHt|c؏zuiԀiIvx8>UTвrR_fa"3=SVe\:]fFmǺ0.Ѯ-#Yp-_]v, MU;!5K!Yֹ\@{ ]"= I7 wY q9 ϸa Q.y#_aСe}^ëL7/;I?cƌA|l<6'M{'pN32)܎' lmel+[WC0jYS:5?uŴR5R|[T16 B}[= x/nMS'6v("@G_^MѠA4nXD[E?`?`C㋅Nt=_ @Sp`OXGxtxs :r@V3iT53zOjowF3aZ\y;o̞ص$.ܖz/͕1u/Q~x8>T9y)O9?ՠ1?zoI Y1׬n廕"\yubX䙩p:[2@Iþ-CwWiO0M3b?`cON/]vٻMʆ_'1?I+GUO DTq,SeM7w/zu2iغUIHɐMmjM8'KTI rd˿aTEBX-Q;tS.K15~WD !gZdꑡ] ڿanRs$S=""@|5|['9|:EqzXI.v/\_d@|SA,iRˣ$Dn^UǤcѝ~]#3m+ h&(CaG1bGBc/ αyL'bʔx{pox z^?oG#T«fY>0K$H;D<*\}/2Uyymx*cxK|[2k[~\)beJu^! =/ى2u]Jhڿ&.ŕ>3@-~=QW_"m;e-[g5K݅KN+HaϲO9/$et(I$}kKN#P:-Fmoz+$058~bmN`0E6U.LxI7&[me+HL9ʧڈ Daxow\! Jbt3oI=PWu&d?&2~6  *\Jh4jue#v7/;Qu3zwtWU%EwJSg"@ w߇P M@PAR^@)*HyQ"A)DIB fn]L%ZV,RR+%$'ljtڸ.[m<&JS=h˄l D _ x;ax)Y|оTiV/ᇏ9| ] H,b8}R%/v oT˽]W$Dx'Un{iHYu+I܋H\鰢G^(QCIoߎrWί'c@6h8YuܷZ}= ˗J,|D}d-Nx]s\N&w˅utʡeMp=U=ѯ)Z{C 6=%U|Yc;tVXS/|.I{àA>uG1ceKy^?*e-y(KOj+Ο!Vo{#8@B^޺iGytXٙ >n_TNmM!D#l*(m -ia/!:ƁS@9T'5]r5W}k HW9fԳ?h?jkK˗|>O%J#^%/9j]ɗC#A&鑼j -:m]%AD@%S=7iݫHßIϲ"6L̶]]ɤCPh5. ܱT5(Rw"Cm"g]zPdw{C 9vRuIOP80j H '9Bɠl{o~Lr|Y,OR<= Uv}#T=q oS߱oj E8^rIR[=-!^rykSl^zޗ0SB>(*x~yփ>?36n#ʩ  D$ 8@1\FR3@Kgph:y0{ɍh]c5lpq ?|2om︯lFRFtD\ދoLE"W鿰)_V;d]dT4}`';# D /RIqh\Jo_߿ØyJxyFℇ?`]Ks ߜ ժL@22nؓ,+Lm%B27nP75T6BoӰ&r"lQ~G;;Юym-^o,_Yאw3~B {5?WBB!W D*o7F-K*g/Vg(mTz"@ \z) D"@#TX {&h׭#xq.<\|=\7"@ σ%D"@@~0<^rG} CQS }09{UyQ%mwСv_g̑hixQNDQ= 5|Ջڥuk43ۮCs+e.D!mL~Ұ( &0{ 淁Z="@ Ī! &Kph)U֖łuգ־U1n;'wBe1xxsv<$KRKfoރe5@lYRx CL8_>Ayb)CKd!DȇaӮ"#f_-+W}ȔBmm \wc$\xS0'"*K+?]Tx'Kn?isJ/~O;%jϴnKpiޏDǩ&oNXm$~_|rulnKĽⒿ/cc4S[7rV0[G2"@ NpJ{FDE^Tz;ǻۮ9r3/Q8x %ɫF"nv)AAˆ35tcC^r$4iz5Woү2=?0"ͼ/:`3x$/ʰ>)mH:taf]k:O1yvY(5%*H@&{e:Y(/b'1f f]潝NUCY';n=[-B8 ok~?{~ì4<T5m_$꧄oŒ!K)CMSaLm=n,_,D($$T:2أD"@N)+Wy)[qI&\-"^ߋޒ+ D&;f~[cm{N'+["HSsLd5zlt#>t8HvHC _"/>HP¸ݍi &~ΦgsO!7ػ]W^rF@DAy}s‡C"pMAKЧiQO0p~w1n{ϭ>я\)?ܹz,O4U+3O2YȺs.na\xmЉf貏MWByzBq -FbqQ\I\;O8-r!F*_"zw\Ti+MnjfNjkTD'-_rVx[^DIAfm3{;?*ܑ@"@C)#~wv:e})_JO[ז8Zy US<ZuaJ/̼-cyn;mkނYnsGxљQwP ǎg'U#/9ɒ"Iaybˢ(%@C|ҿ3}u7`՝t7+•~6^-yQo/}j'ڛXvN`J*s_B8S. ])N3~ato}_>)r ,@QH̯/!Vk;ؒaM<)@|˲X%x؆?>l)D0#~S̮lF mߧx^J V땳 _?[9y'W>B&hV@WH˷~ethc&$›g0x"ln9-룲y%IƍF;Et'D&a.T#ULi CoL IazJ:>łڤ2Ε#Υl~  ]uWG{gļ%g^r\Ȏy(J):hrqWY#فқed&D#PحQNfeծ=37b'zEbX4LYIא5(f NL[?Oȷ6 ju7lcX<#Xw mbXT6+FJ=߬WTfl0E2cY\{sGrZtǀjwTg*<4FgEak_5I1y 'eZbͷ@`jZ$<Mk,+=z+u ,}y ! 4[**fbs9.M]jfGf?;q ha;Qbl|9_-R{zӶ% {i0? 6cUrk4@;CPڍ$4r*; D4Ye'>lp尻~f=*~:HO󶬈 zj`!f0S|#lFRz[x6wՓ#;|L^r\X `#Jy74{?/UJwK$ DSP.c[:z6p\Glx +,Y|,{6& FƸdr,Sc&RKB*_"s~fv|4ժ+  ٌٌxvnfNNpS%FR@1Le"!SHR.M+#MH޼yF{doDdWL{0g TN"@x Tm?1 `iKc0|؊sT v>l ( 72ZGg]*4-sKaG2aGb%QN}gQaqx^lSɊ a@I xXx=Ba-J]ݎMYܰ4[l,Fw : ݦ&JςAyHFoe;q8N'պJ;!k:39_} W ūyw+~Z:*\nۇlO/f$9 O;;1m)iؽs{GV Ygr|$ G)xz۸oÿ XE?Q_1&@Du7cNR~M낢S4Ht,!ۜ`d.G7U.l5InH]vx'79KHHS]D" 3jOʅ›ۇ'~2Rq{'?}6gtg9%ў 12OPI+>F V+Ч`|2'X!.S~r"a!(Slz%b[/7Y9Gݢj>Oo%Ÿt= i%~"S 9 D ȽX5:aD.VW-vl"#Dh~vFDٻtL~/1#^>yӦ1#ۻcO>W{6 嵯*VIף%) ^L!S"']܂]TYc#$QbET4 , + aaFJ[=đ8[7wh/o7"Na7"rҥ{Ӟn ^% 6x`3URU`V E*}(j*R*=q>DVA>cڲ V<ʅ *_V?Na~to6~1wpxe~E[Gxg;Jy>Hf68|##KV"#<4O@hTHKN^t=h,PJsbx7E#{ϰV抋ΗաyP:2;ן)2^8]^rJf 3ё9L7OS)LZ5<ʅZ}W@jO-dW[</9T\P^g:D*V-qk-N~=*[~''%-_lXE>eKs=T/|~nG% ` g_6bvmGfW8xuF9XbeL}@ޅ'o-6&Ge4?2w'.*N@"/D"1Qi_STųsFall 9kI;IwQ0}y"+$\WNXt[vLJ{\Wzc'~@֒ip_8KS'"@v"[{v$e+%*Rٌ=Ǎ%0s^>*Ĵ ?E9EfK9hE;فy(yQ$ddq;řsLh\mb ^™,>!بY^V鯜ry8g*px˗ BbJ٠A>*b4 :YGVmw yez;^Q˧-$H5>+We^?~;q)ŕ}\;vBNTNDD D}vEي"5zT)27|}9P(7hUVnxۃvrkV0gUl?SzrdyZ?2N,C!%9a5PF}4b ί_Ņt4.AوP^PsNz$C4 ]Éػ'G/@;|qѮb.peWNT76YB\ARyGAx>n<=M4XW6y#WfhР,O 6ᯃp0gQ<2]:ǢesmE; EmD̼!O î0wv\ ]{ }K뻑KE;^w;4r 1i\;jR~]E%U=c(gH.Ő|._ Ceϭ$Y))JfڇjzLOa{R.D/%I7j?oGԮ Cu~&UjN=!t1VVx7h?o+7M3_> }iM?؊XOiRvj،Nv!9UML,O8{f[|ޏۣk8W߳e׎V{2|nݰN Sﲕb&mBK"$|P`fOPTtbwhN^!U* ^5z>Q.ۧ5v=|8ȋ/9jvr._rtN'k>()zaw%_:@t᯽Y@IDAT0fG1wQ ECݾtML^EbO}Nz aJgx$$!(,, XGmSp_,oʨ@SI^i0E%{}(*ϻ~We *_$ ϓggaaKե,#V Y<'3I¼``նTJc0(OfP?Ƿ<Ƽ*yx Ya[Xxs=(ƏpW;Vw.(⩜ڠ% "@(@N y3LŮ+SoIPԒvnO7uvti`߂ah2G,t q,Ns|q(wuǭzv4oK2O|mJK_;F׷wJ EObϧej 7cq\ Ƭu]-b^ݕgn-;3/O͠yEabڽ8s-b_-bhT Ux okEJ 잖!ťbiKG>ɳfӲa.IraF9TybWoR5lwMlNd 牍g^q c/_x}c,S_k8nZLqim]7rqYTNm D MxxƩ"5BD(MKgph:y0{ɍh]c5lpq ?|2om︯lFRFtD\ދoLE"W鿰)_V;d]dT4}`';# D%-1tR.[cW0fzDyGIbا?̈́]Ks ߜ ժL@22nؓ,+Lm%B27nP75T6BoӰ&r",=Iڇy"AS*uR T-Ih׼6^7փYאw3~"ө̱vٖ%q T 3+3;=OT#CD"O \O D"@Ra-:y_C?  |2!"@ D$"@ K`<8x<%$=ToJ9"D"@ ŷ"@ D"@ D"@B\0"@ D :&odDi" PD"@(#f(WD"@CD -+z_MHvw%Ǎ)(|2H D @[z@ D"@ D"@ D^h^$I D"@ D"@ DxR|{@ D"@ D"@ D^H͋$!D"@ D"@ D @o(D"@ D"@ D")y$9D"@ D"@ D"H?%"@ D"@ D"@x 7/$"@ D"@ D"@+RA D"@ D"P Et1gHNN.<"@:^ux.ꐝ m$yœx]TҌ5WSpt,XÃeۮQ/ʗ hq%ܷkw: ^r<"P$xP+=22ϳqЦyJۉ!ik|]N tLyr4܆|$hv, rx}ST)e[þIn'HϔUkV/_e0&w˚ ="@pD@npA-7|0hpR4Oͫ_mE:-tn]Ǫ6 >hQZpi:#Xu9[ط֨Ҧj웇t\/KpW-lC௵>gGNKt%D=UJ/9EglA ?ޟ^S0Qxس}}fuuL- ޏ°xahxm"^ ?`~H~%3 D"NQMɠMajRkmY, YD]=jWEIQqr'Tv ;cNr/9J.sO |-g`˚snjgJo L@^rl%  E!Wzx);1|xbǘvYBJ:NGޮ,E:'$#i9R#ysmo^JDPP `9{;KRdo )cI4 D"CiwnD`65L#,Vk!7|ګf4ޫ'wfJo3r; ӷ<feA[go1Tg>,r^r͇po^ĹpEHӠJSh^D<}^ ƴ) KbÞ3+[ AZ5P:-:48C>(|y ̓$qGȗ,WD!H/.26FR*SpkFs RKH"@ DiN){܈ؗLTz;ǻۮ9r3/Q8x %ɫF"nv)AAˆ91 &nS%? %4\7bWb͞Byf?j CpⓂ?~ۆ+0yVRv=:s<U:.9"!DsQNݮ=ώJ}kJ"@ D 8AIm"}Ox:vVLn(#yN3ٓ9N ! 1v^߼#1C v7š6;^rϱ?? ܼcv]yɱ9"P =t>u'{ϭ>я\)?ܹzL0?iֵ7"Vbgd2ٙȺs.6q@^ONp4U qh ԛu^o>P¾yul\/W׎mIJN\y*ʗHUNty;P!N[ʥ^DIAfm3{K\r %C <8|ۏ“$u9pֵaa|??1aJT@.1L]酛^re,AmmM[z1mN/:#N qQl;j%GU8Y"P$ =t>u{鱕Kd[>]xwD,;d08US;cfCyQpd]n}Lۊ{PD*ev֨򝕁`#/ޯN_4A' AQ\5@Huf CX+ؙ.ᬬG D|X JoSt~Aftם!-X}/ZawrˋAKD C@$L|R,`j+SdݾsGv9Y=YiY*GWUTBq/5kyi;M׷/h '–ngc-]5~>d80CwSg8kټX˵#WM7)ٖ"r?3=3K㹷b,V-#VJ >c#70CBhH( C^&{g]1Ps`S>uz W U&'Rw}($f X`K:Z5\V`+_TD tϓ@~̐كx^J vexr65bvuSz+'dq=T ( i֏N74c8#uBǒoޞwd:c\V̝m Ӳ>*WdܸnzP$Aw"@(hN/<[V0tߏTƭ4h#X,xoMj_ <m#¥l~  ]uWG{gļ%g^r\Ȏy(J):hrqW 'Jor3i^yҫVi:kApnaƺSn;'Ǣy*:^1ZS!fa ,#9-c@;k0F9aw9Þ0jm,x'zEbX4|S~ov O!qDˊi&.uފ?D+Ke_ABCd9QBUvf9Y;~vʩfÒwXXZrHKcZHk./mK4Iq &1fG  hv\lfBj7 FʩHD"P\w6•ŸGĪ騣MeEdS#l| 1YG'aJo Dzks_=yL;1.%=wO#B^[^5t̼H@F#GM/9j20WPz^߉{Q-2 ~Z!Frj+e#9rɽI۝eT) fW&zX)LTj|a¥Fa!w?%R=h|)T3 B[MxVPҼ*'~ *8"@ @* hҤ?bQreCVvM_V @q`/Xgpͳ{ R#R=O-x)Ƿ`wE*/9i7~5Vn؂-6wgv;`xs kaG(Q݉x=>X%Z.0>l;ӓT}[㩜8q}g3ߟLbA\&hD 45+hg6/_{ȸglJmA\0nkdul5&c6$O˅(S9TDa9ͯz]lF||<;jR3NNpS%p7np\{921Le"!́) qD xZ Ių`_peD iݛ1#uL품l1\iof3sʩC @*瓜>"l:m`,ѴδVl@L9EGMJXXfۛ] ,=S6Sena)HV;Q 8)7ݘ5nF7z۳Mkr\Pa@I xXSZs^r޳tR4븣%R(<շpU!P w{⧥30,6zmDa&-J>ǿûכGpÖլo;;1m), |bʳr!KL/d1/ Dy +\^)F$Y8 x i'"(Gy޼8HNǷLmŬ-7Kyq)RL@!CS#ӻ&7+픘LyOx=)f 2hQS=|>%z8֢ɓG3.FEV4BXTͥ}6gYeE@ri#T/ Qz?=R2 :Ŧ' kxiBغDykw:+G([AmswImח!9-P62ܸdOr*!' D0Q| 2Y(uF#BVEe6͢x\_dd4h-]NKUY:&pחRI'u3o4f$v ך~-u!ƓK1S"@@|]Oy?64sMMPۢ];79?~M{]g2x* h\?㡚,aJ+[skWIa#<- \\1C˵.rB=@xxI*$-mI'C.mQټw#%l irhYgPSU,dI L@ sDΊw3ⓜdhZ72k/2;"`#H%HKN^t=P|Pj[yq+r D(3)sx9␮wxg 3ё9L7^S)LZ57 gXrκay(>JpK'g#Vp8CuZmu1QA[&dC J K9 ̧JzY ?|H`WPEbqGĩ*yc%ǎ|wZ5J'%D; ЙWzx)_ml(!]Boߎ宜_OƀnmДq% oլo϶gLnYu+I܋H\<'n]9TIzsASgA /¡7@h(s["QEO?}I׍a-P1}'59w Ƅ㒻orj.5ۏB4/BN <xPﴯW*9\6Xlv$I>} Ƽ#o|儏Ey_[=w yɱ|ٓcm?kS X{qꙗ"#ODx5ZdމWzxɱH?iBfAK, 8(: Qtgq;řs@ pdg;q^ʭz=z|zWK#",ް>* |˗ l"{Nԃ SS;F[VOnN[PHj|V~ ꓳw S+݁v쨅<扈<"@nۑ8,RWLAs`K}Ve*R`IA;k+E[Nol{*6͟)=92|<~b ;sݡ| !ǝ|Vy2 $^;c|ͧ];[M.%X>ΟvF xɱr D=UrK"?~pM5 GUX- ?a7Mai-+#(-__ r=d(=z5~㴬*3ؠЌ Y^b`wbX;vIP9T$̫2z]JkʳߠA> b㛍A@aӊ,D؛"u:J]'YʩVz D&xz91gdmt;1FZ#2tXNNX ԩQBWq9$;K7|P6qv0e=W aA3$ '+app"I 7hܹ?z \S3ͦbWPdkxq9#K KWLsq7ARz &cDih ޼BT34hP 'bCH8(.c2\e`ҹ6b^rНOW}+=oI6T -[[jDq Dx sCn)pK||+~T >RdWKWQgFrf0!O î0wv\ ]{ K ﻑSD+ Aw&6V޹w d eҦP2$G^M)kBo(KxV˫B,EAB!FvIcaܙ?Ͻs{{.\Ը~;[(un ,@`ʅ6aҒ>eSYA̞h?s_MvTV yb[?XW)UҚ,q%MWՃ2q`a)uv'p'/~/.* 5^ fdGބZi\nS~//q%,>xIjorcB'wv5YDx a)K|eB&H^<X˒% : R5f$&y6W]M;Oo/ \˗3$ /yv#b+ N=6Tu,7Eܪ)/6i);} txuB WD8 9ǰ3aЌbNwp4_:ᦈg9uCK   |$y&'pk=^3;w"mhjI,IH@vNa뜡h|oޜLI?`ozn>5|qh@u)Vfdj[B",9 4_.}y|e;%A$PXʪOeG|Rz̠E䙻t Iw׋Zʯ? u}~ֿ9XGr~%ňy+mo(]23^.d;m *.32FL_ʅk=/rXȅ]TV=*RYU@+G(HG'BƘO_gφ3صl4Zyn2[vniB?SC;~YN(xC$@$P |H@BnxٝHZKB9>}<%''u׺JI0FG#ݳ6I>'UGÐN@"Wzg\8]pO\4#`1)G}z!KKh$ _ȨO}zsoČ_eٵ 18Q #<UEͣ8IF_:|♃D,Irw KrZb[h\G!K# @ NB(d]P\б\ACIWfm֫`Wӱ:iOATSoc ʑ>TNe<վ)7 }6X5~1 [/+Ҝ/rM$@$@$p0[MtN|?|:7j͂>E33p: 9O&S ,G.#us/v W*+_ࣗ.A|\3"K\_ mۡAJ(mk: 難uem1곩P,_G͔axm!dїN[ BJ 0k(-iӶ̉}%D$N`n5W\q>r<!5ͣ,=7h_f.ouՆ߁ޱnV4=xQ/,-YR\2iAȈ[>մnW_iXJ# ] ,݉T?~;JHHH@X7D5kR*_W" Zt@գc-c`y;X U(.VjA~';9w> `h#%G+ӯ{~dރe%@GY˒WxΩZ{"by(S>/.Pwɴ!({WrMeZ)R#jP1hOtzLŷ?щV7cDJg6f]vGTI`8vd/A)"Sm?r)`rZ&8s8SoJ^la4%:ayO$@$@$OgwNB/L5m#,h6#q*s/M[ZBvf2mdž=p-&rc :CGB sYr4^=G'q.̀]u+D$Ϩ)Yrɔ1\<cc8y6 kݭ8Z\vם%KN0yaX 뗀ECg%8-yGύ,> nwc[*lH;e5>42p޽{t%ct ( zճݪTWP{1N;G9v jqʗ¥{0oww VN.=̓kB5d^O5[qƙ@^";E4 cbCj*Yc+CNn\ b 8]Zz<Μ$ͬ^8w%t[.+N2~07En<󥴰xm* %*׮]ġ_fKig$@$pIYH /s"^|׽8~N{jVi:Ri+?hHGiCPV hS˦V^=y<۝eɱ n55"=>{+KpZ J~-Yfb-6_ŗڢ["Y453V]:== u+"r"`<O?~O.){j lC:cjCeѦ߄Pú1o]CuxNHޗrؠv$Sv,[&-˨im*g 2B8evñǁJo186wėuz%L>}:ᓧ:bq3F}59 |y%DG;4MYf{Rr.(㰹Х!*W3c/vK믲]GnA$;u dfcv()VE{-{e̵a)1w;4\z -HHBL'K_]ڶ-E/T*lNV~zs(5rno';78IlȒt| l'c߻2Ys&,9?.3m*cRwcf /dH #Ҝ{qsK!tn|CV8ìwŧgW2(fYa*U|F)ʉGQhc4Vuw؅Y]43_HwljA8}, 7`l@Q~ M6r͗=-]D$}Q -5/4ulb4_A%KQ9{K|wg,]7Q==3u3=+fq,9VaHF&Pf_i^Q(?MeG]ي=b:/{t!M=~#Ԫ 6RD_KOX1x0w NdQ\kBwRgS4˖epSlr"ԕT>d{\( .e}'A[JA*E951k<г}o=~~cbLG#G*h{teȃM]p/T\w8P'ή8:ʷM1ۅMy}RDZˊm&)u3ܻ: L}e 9,_z{ rlN io֪󒙙iփ@M1(>y^;U>= گVZ'sr۶%s`bm0,M(8@IDAT*m5EkG;KwVs0H=5S; 7) B̴Ql_1Xf8:6- EU{K?A3ĬC䁆<3c=Ǔ :[,9~Gl`Q#q'^j@%!7$@7@o+V=ٯ!Ö!1Ayj\!Pj+3|a;(avo}kއb6GǣM7*]MvWnXNp@$@$[!J'aÆhҤ?s¶?YGɵfm꡵> I7u_KI7wvшwASzNYݥjJ1#Ut컹hZ]B;+a~;^P.X˓%.$@7@*nD9e*$vo(ӣ>kjnE Ttt5`[=[Q.[m5s R.Xg+v5lw7G_vf;18mP|x,45+T]F71c2.36/f]Uiܷ)֍zQgrc m"[.2e'yQ9AnXf8j3R3RNq%鬤jWQ4Cu*ReحT# ': JUA 1!]urgt!Gv 4l5\#(R3GS+"C$@$PU횿$,:?-C M`dm{%|7b!IV"JǨ`լ8WSene)0;[,x'7/ѣ`SxyvQJs*3m#YryC$p{hn6;Vt <0]'tmOqf{ӞP4s&yH)~ٛmz'ݺ`8KYe"989?^bUqWs:|d Mlqo)627@?ÿ;wR ZnַP@u={-%aNXsХoӰw+j$aRI^?waTNC bqDCO|׵#6o'45p}6h).7g۫\:%3eow\1۷hII]qc9U(" (vʰxGtgԥ@ITiM1w!6:YDSvLdǨ&^4#ک(JZ{,9^6cEubf_e+Rz&n=bWqLzŔh%˗cy[-jx,]ju_kL}7cK9GɢΈrDH:=uFkD$ [ڂm{Q55{)ɿʡwW{ŁLK'uϺ5f~e$_-iTyQȒ A$d~sjJP)OYW TVW(KF9vUnʞ]8,SVz ƘyhX9dȉJnMge:QTDñ}| \N9o ^%Q{O86ʴX؇HӶY}ԴmHIՇ1P%*ל?c۲e %FrX{v; ipH/dUHbQ^\tMHT;n~mRʣ;!.w9\b% @ G@:#HjָUsFF}ZX2`KMN>d(nLéΨ oW,9yCw @A<4;L3~ PO7e^Ɠm|s;A嬒C 1l~fߖkCd!.( A"Dw}<Сfu;>tr1vVA[=|*6r!K˗}\"˜G9<+e$c+%$>OhƁmĈ*:hH3Ʒڶ́v)ݙІHH MCš]ΨsOy/x}2tĐ/;\ۯDOjϿ$q9yy%ǟ8H < =Il!|w%3`̲RͶ XĩXjױq$Sj~{%qk c tЏ8E"nrR"8Ōo[ān8(Adnnv["׳^m AWBnHLPf*; 9'Zm|x߽tu닄!8} 6Љ?F/"ᗮrs5ar%p+AEn*Cke:rg_o$"I?Yr?Xt:c@f@2>={fʤS=L-2ppb9 M^Qz z}-={{XuRvv3>*-4-sqW^w+;~$:$= f+e|yj߯nN[˒>I?RbTf/yf*[#ާh\VE9IZmo󩃾og8%.x^ րO 8.C-K[Ĭ Va甍mlSUhq w䇹r!;p*SZDaLA苧ч` 3ڔ{K{:0*!˗u9-Ncլ)>/?047^I 9+MPg`<%ѫ,9z[y}6zֶ)x6zk6.7oFiG$P8 {,1u'btt e6#Š*>v1>zNo5Qz^c/-Tk է`4ߒzoa2*鐺If2*x_UkІw#םrMqReMȃ \hF˗d߇*o _fW]YEYE>*aFbw:O uǖc ҲŬo:}Pʽ s[}E8~vXNK$@$P)^y^}~%Z2LL(v$-5bʶ}*hd1 g.ͮVujÝBqQ;z@;uQ, +X ͋cjgNlټ{NC8|qxݭ}ln65ĕb+s >J|(Wj{}7ER [߰ "X Neɱh*6jˣ f5}đtktw.?)%Ǟ ~  <}  J9Z.VcSkuK(@r:}_Ǐ?Fsz^Јp \z* v2&Dۙ{ko(6.~UpoV엲gVZ\mo{+~YU]+~Y_k/ΣHxW4,gW]5aH綖k LЦ;9,_Zǽr~Sx,3סU$1oGnŐ'S,]clMϫiF$S hUC g64OMgV{}tuVwآMO;kdS+D\8ViHH .W*Jkfb'ƕp6]Uʰ;Ɓ)<*UڝįzUTx5yj5zqqMaڿ?CO#KNy-_rLTN&h>/a",eѓOL(z„ɋG`CkYrDA' BJ Ci?(M>M.St9xN1\r[+Nh fBQ&{y^.L[%1[UG,E‹r՛X1 ~] e&a5"bPaK đ>x#aney{˅sLz0*o*8yv#b+ N=6Tu,7Eܪ)/6Y >:ٹk!Z^bH+"|nc0hƏ_;Vt 8/pSij @>iJ yٹq8hCSKza9_OJD<˘u [ E{fJ{ӣ 'w;1ࣈ%G4K-5Q,9<)kվż/bx=,9y>H P}]d9Vg._qu5ۗ0+t]?Z@5ܵ=zb0 |(y.݂gҝ^K5Tzo;b]0zt,wa^b[X;tK7,=EX57zhwMZfëم;m *.2Ubn|)N<|^[(7&SjȌtx#61wbXM2deѲ;#)ꖦ9)eڲϫ߸nXNݐЂHH  5NqBnxٝHZKB9>}<%''u׺JI0FG#ݳ6I>'UGÐN@"Wzg\8D]pO\4#`1)G}z!KKh$ 3~e2ļg'D(8KlNVy6ʒ$Y]z6G|(ŜT.gb$y), i!o%q(!xI?\3%KF;Bj\(fkz2 yj|~'\<+gN[zԝ,9~%ދj_A(+"::2ceqL J@P\J펨{`8v`Kt#}:۫1"Sm?r SNC].UzEKU)W gpSz_=Z`*^EۙlVf KuH) \g|V|$dm]6",ϲMa3(cל+k'6)ɋ!q󣘳a *#涎8QQw FYrMnhCNNs[c۞oCF̼9]@F%GJcطk!-EخG4BؚəEo}bQj $KK h$(d}eڵŝvwv:OkWl(|zD( ~חFO(@c{5м0 (dP xp $΂vVAqKl (=} IPρ 63>$l9Iz&E%pfJ_6`˅]xrX$%YNCY.j -x"sUR6]nB kq$ټ?0aBJH$@$@>IYH /s§8[rӧ>y#&ﮘ{+׽=#mԷ^!$ײW-#g} K3ߡYǗ*փnHhA$@$b>).x[%'$d=m[E^ƩU؜.U(526o';78IlȒt| ;FNƾ7l*Ws&,9?.3G=Rwcf /dH YC?DGE}ޖ/Eסε慦boQ[gh p~Hu GY2(fYa*U|8ʉGQhc4Ƀ,9vcDj"[,ŸA8qWL?xanբs˄Ȍ)>C(7 ;<:%DQzr^?m ,) m&V|\wa-*#Ҝts+_EQDk`ek(mYjtƢOl3ҕ)ge]]t&Lѯ{buIXst<)-QEr^U|/rthJ^ٹGa-9QTȪFewr3HH򇀪IΟjZٍo#)>V''+-րPBeJ\i|4KmۓT_*K1_Ebj0H%LJ%O3 ݈rE߁[(`:8WA0C$p#=K/{-+bj<I =RQ;˩qJ*# ~S"Ԫ 6RĞ;=b43K>ܥ/8Z%GIz ]H=t{ENX.[7MQȉPWRmzU}lWr{Zf0'>ؒm`uRIfF|E|ف+Pþcէ-LxkJ;j{kT\-it;]>LJo };~9zk7b*_X>:Xi2ԱnsVwj~{%GSk̘f ݲ$8w.z  $@$@H@_Ü G[V0dWxqoˏpቪC+-H|;漰.r7vnm~8<& C>$:c< ھ0oY)V@J17}Z(y(%{ a rb'obLi~ޱ=y+/^`0фh&D]܋yS^ef$V*̒wgkYkڸeyY5㘟^ܑЯ%ٵB ﳗN^5o6IaKMi-+p: **2uQ.`2|9^].,C0<+NcmYe>.C߬U3ufFzYNFb55ƬyQhV4wpKkZIkGDϱmۖlqm0,M(nV^f}`XRjGrj'_  &}Dd T\WӿbpsDZx$qtm>M[׋"20L'1Ĩo VH X>u.mðΝ,9~Gl`Q#q'^j@%!7$@7Y`䬛0EOmŐ>`wt4 0!=JoE\ڷϵy2|ں1ɐSZor>uXqʹ}՘ԟ0hDm?";nNcѨH2}=Ǽ` [tؑ圏{9/[#zP=w@LP5GǣM7*]MvWnXNp@$@${!J'aÆhҤ?s¶?YGɵfm꡵> I7u_KIn,1ވwASzNYݥjJ1#UthZ]B;+a~;^P.X˓%.$@7Y`(}e]W*t®f ~$8H{eUӵ]x`"[Y::V]mrN(Fo3Fvf dA'#۫fl^0ͺҸo;R2}3~qʱk6`˅]frX$Wbw7PIDul-N ͫfqnffg̗Ҭv/Ig%s>jWQ4Cu*ReحT# ':uGbBZ6#Ctlh)kFQ%Vg+,VDHH5IX4u Z4UśלYo}sQ+bj*t8qQNBh@0T̤_RaBwؑݏޯ,9zN2^=o1 쁮6E`! 11V8%!7$@7 YàYs%8ճ219v~߽ ͌m;UY\W*:əzH>O'chbS+o&9k{5L{P'KirH?WpB)89,_*pQ.FbxNJfv:MY̶N`1d:uW~OA}{>d]JCճUlܲ;Um0hII]qc9U(" (vʰxGtgԆ PU;yS +tŝ/ j1=zb21jk4+bD;PER~%kbtܿv"yl.Q̬^K,9~EJ$@ Y` :ǪEZ^c읚V@f&ի3~_d,̷3y+GYqT3\IX އ/~9!z%/8;c{kQRmvkVu@`˅]|rX$`Ű"+Ds,YsUj]\KҥV+g ]N(ɗIq{:0l]by7uVfh sG\~{oW1O@QM&KN^t1|7[C'um~`sTwAؗyѭ͎vKdzY߫ a7?7(+gYY,@˅+@|Obe 'ULge Хm;̈S1Ԯc:Y"wjCke<7~7+^nNgbwY^ (RT [>گ^}ճG) <"}XN}D/$@$@Ao_KTo5լe }Tz?[4isNMUѢEnq`xC.TV%Ǎ ˗pȳ\ɛ[W*Jl?eez`c8aյ {K_/ux珟S2h+}a1u<=SΥZUg~ipX(ŵUٻIN< @T2O֯L"sa5/ ʩ_vrj֊vj3ߋ9bjk~Ǜwn$L@S&3sg^H׭>z=kg鉴5zk.7oI @# {(K3Hug\Ӱ}PC_jFU'2w' l3R "9؂=j_4=[ J$r{#k> U{U/aLЦ79,_ZH9-"tOj=-h**,2\ kn6+ӱHY\'l9&[(A< Cb_QmKrjEHH )ϫԯksJũIKXcJ1#9dgW:5NP8q(=E\`ʺٓH}CűT3ؿk'lމ='!MѸs_T67֚YJ\ Ky >J|(Wj{}7ER [߰ "X Neɱh*6jˣ f5}đtktw.?8,9vy%(d}eYW8ȳRjW.,^GʍhXީLvaքubZȬexk$;E+ߊʱjEw-4#R2:\<5N߃c'pϿr"+ƣ^*4".>^ ٕi EN(ګ+Hs0B`|iI^F9URXEBi&YD;*u+<1ܭRda,+eSvglW^N7"9ՌJM@ϬN8i>|2nچ?ڣcҿ[^?<* Mq[rj_  \((WtU=(`\WkjwW}WRR`FvMA6Ih'; ^#K:/g9&D}rg\E4z'_&K@_=aΌ#0`!,9^ @!% {e֌a5|#bPaxa;ߵI7!6v9xN1\r[+Nh fB[!>FWN'ڦ7 .˙d(M2ʩ5}A%Gd[izDlidž*hV}ʭlّРhn.\"i9 f;pcEaK',nhiA$@$)[^"5gFxqsfvE;+Ԓn!lD$N Jp{YuPa7oNn7=prN >8Yr4IC䲔]+Yeq3|e>_ =%' @a! {(KuWkNܐ~&F_@{̡_Of/rL[`-8|&i_/k)bƓ R/o'#}vh{Ck|1ef8ج;m ar#-1zKp gE˗*Q.űBC]Gf#=zM&о1nٳ! v-}qv#)ꖦ9)eڲϫmЎ_S ސ C||)жjv'VARc`cǺ2u0OIhA]nAR`2шvGg4{IFuD0l^/'%YlS&*.XL&D)aQsCȒ<IH <[hwWm+,V2 yj|y&IFg$>x7MYv-.A{v2NT2118#;dUg,9NEѥgs׬2,8n@rx v~?K`gmdB^զ ZeA WA>*^.ab˒J8WV{3GփAc`   $2 F@V$޺۳%M; ܈Xnħ< @(# zWȒ|J( L#   T|˓`:HH?@IDATHHHHHHHH[ F !  G%'1M$PX 0~   (# g+   @Zf"lv)%KNQ3 z,_3$@$@$@$xeX<&HHHHHHHHHH@nu"$ *10$@$@$@$@$@$@$@$@$@$@P-$ *10$@$@$@$@$@$@$@$@$@$@P-$ *10$@$@$@$@$@$@$@$@$@$@P-$ *10$@$@$@$@$@$@$@$@$@$@P-$ *10$@$@$@$@$@$@$@$@$@$@P-$ *10$@$@$@$@$@$@$@$@$@$@P-$ w'QMB8B0GQDD" P xQ[(*"*U$ xpddgv7N`Ѱs>gvvgy@@@@$(     ]$$     #H|;b7P@@@@@H|%I@@@@@Gvn     v K8      @!@@@@@ m$q@@@@@!@B     %@.I     8Bķ#v@@@@@Kķ]A@@@@poG      `o$     ߎ @@@@@.vI@@@@@$(     ]$$     #H|;b7P@@@@@H|%I@@@@@Gvn     v K8      @!@@@@@ m$q@@@@@!@B     %@.I     8Bķ#v@@@@@Kķ]A@@@@poG      `o$     ߎ @@@@@.vI@@@@@Ɏ(@.}ZvcoJq:Z'W50 @@@@Gvnp^!FLa]TTT6љ٦F3֧85DF4UTZ88;ی  pgBMWFU+ţZgdԎ3վIř7է/V3S69o_xV-o& Upk~evn^%KWFFvqc֖ B#͝BRDһt=i-iZ^]4#@MD@iERA𢻻X  8Xķ)\2U\KRR"^WA鴤TOsۋڶ YTMQiCқKL^tp=]ME^V.ez+TNe'sưkMv]le^ՌzuFˬ &ZHlL)g降5e\ݱ YH5H;;jоcS@@j@o6 MҲt\=UwZޡq푂)Bj$Z9 nyStB&{weK$#5g5=vmlw^~[kCֳ~sq;3h.t[~I:{_5uH8zI|Q: 5OS@@ՖE-15%j}J@U]q!cPeIoi{OE.rZ*lCSO'qQv)  p _p{%M7tU=̿U`3Kaئ%[eY6saO9xNiԖ[n|wW|lԻy֯+^1g+Jٓ]q*y/[7Rv[ Pۚv~n{ܐ,yG1M*yXE#u;Trg9UZqJgڵ6A`nmdT\1\?k33Þc `΅\fgYaD @@wE==[OR͊\7ZK9Fam77iu̓v, Lwy[>;(#¬cߧ뚇XI=qlPku5 sidK\>Vq=QYQuŒ]EE*,,4i`Pm_XSקBGOx۰"Ar.6_bbW8pcygbr7n"^@_ik7̌IkzG4iѼ"cիma߯Ф)S(Rӓ4-KٱnG$y?j7OaAcԼɿ%uʬڮ8c|~t.堿zv\5KӮ;使uɲ'/W;z暋4k䅚l=I.r~^ )aŝwgOXK;pޮ^1Xdxf@ Pzb&&pv5-+$,]r.9mJ084g}y=#_0Gyz4]٠Nޤû뇜oC ul̻9.hG+_Vҭ&TL;ejvlx9pM3M֥Nso54Txzs{A3ui)ӿgې4{Q+=DY){>diz/"bAW՚;{(X1wI.yWFn\V4vێZq8,I:@@h=-I"?} c44![GmMիVͭ=կ=J:A)=bKs74~VmZ_R.uJBXIvN+lK k;|U6LDO>7\.!+xEMo\E땱55l'tdEuHqoG]+?,yOnx6(Jnۄf/1^+ˮ8>f4 lhԸ#>>2ٚodߥO֥q1(P?=_D;.h}aH ߦz6r y^Nn06nvGt'+KKVӖkķq4mb[.ӻ63?%953c'kڮus^;C-Ϸٮӊ4>uDžڎ8_1v@@/CYݤ^meeߜ%oi==j˺M([TYؐ&Szρġޝ~6o_[e:6ƅޥ3%~Xi;k$z\&/+  @@E22ͼ(IՔjx{W8+v۸չyh]`<3֭?)';j`Uӣu \5zv*8 {*ߣnbp&jF-s%F,ժn"%t^@Szw$]Ud5מһG|F>ǾWE"{2!]cxe H,Ο_Zz]hduۨyȚ_^5WS+:k'{>Д6s,Aiz("@@ \$ݞO5cQ+yA9maoFڵ[n .?uru gke`JUԲ=X-wP,J//rj$ra8V.]rWị04jwGoG lXqX]ڲ)lD`>z\5X #{c5GuZ< np-ZKz译g0pquevY6NrvK  )pH[pUixLW?v{%Z d ˧(/)#t s7WT+P5C[Hgk{5wԺaڭKprj;wa胞W @Uvĩ4o+\`Z%hբihMw 8FS;8.lGx  @u T/%uP%3|ŏRf˒!g;,!a|lW<; 9~Be{ulБ\Y۔_lwZozh\D?  8hҒ%]恒^~@{%/>]ͭ-տ9OcBj_ݻ/f#:M;T̚_;^__]6kժw\>5fylg5aD p|%K  8CR:=.$[Eo V^}Þ27>MP4X`CWnWi4ʺ6hgAqxe |Dku[JbY{oV|O .~6unnVN]?Wת̉ʺN:^Vm k-= 0w8vW\Up|EJ2 _ l?*zo%=J-T7Sշ&WtWr8u z[!Kgެ/We>J~/%UsPj]=C\u5FWkFOV컍l}2ͺ;yr2oQ4C)&rmW2j~=9A 6:22pqa]Dž]qlWF  G@ȯoƂДG6xDL^E<熇ˆvL)Wt-^ldq??[cLɯoh|2g>cMoj%qn ShXzkݓ9t*Z"M ͹"9TnϘ򆶄ϗ -=DUUpi@P ,zaǺ'ޜBṓuUw˾.u5=>G_m-jaѾBRRU&ozϼJc}k^q)< Nt'҅%3Q:穥X]/KJMQckĺ{:$NWhVOD5S fJfX5#@/hG&R: t4kO38^0:&z~yڴq6|U۶:[o_޸l &RS0hjLߞg9ROWfb.y))ggw5j.^A~J.砨sBj7lQ_?5YlIk_`LSXPCC"0g ֫wn{ 'tuY f{MVFc_?k\lzמ;YR3E_X$Sn55k;QEP+;>;.xՏ` d@@ \BY␇ԅ/}^7WA l+ Qa;ެJYzFa6.;iaۧZ)-)KW<6-h`G}OҸZYy+jσ|+ʺ=?4H75 Bh!Q`} 6[}T΅wUXr{Ƿ+Uuz-j諝{O}펲+a 8_NQ~90~\N!ߞ|)QNH΃boD9MdJAZpq[<.#šJ ސsܰW丈d؊ 7c@@ \>9:-SRrծ-f -Y_V;÷?YG70j]ںs-4a7gد *lawY3^=Ѳ)Ή:Yw0\zvVyڹ/MRͭ_kWj{3b p t:z]=24iۦϕ=Aͯq"|7LKq1ӴYZvjו>Eukv ̐{+1jXk>]jm9vּ3EW} [!p@,YtQƵUD@HPF%`q@8BV^wZK\-l(⍀  p0*K`u   xfW7_5rQ  #H|;b7P@@@@@H|%I@p]+(rN  @ SkI  Pr 1*rIe^ fjWl   n@!@@@@@$     ߎ @@@@@.vI@@@@@$(     ]$$     #H|;b7P@@@@@H|%I@@@@@Gvn     v K8      @!@@@@@ m$q@@@@@!@B     %@.I     8Bķ#v@@@@@Kķ]A@@@@poG      `o$     ߎ @@@@@.vI@@@@@$(     ]$$     #H|;b7P@@@@@H|%I@@@@@Gvn     v K8      @!@@@@@ m$q@@@@@!@B     %@.I     8Bķ#v@@@@@Kķ]A@@@@poG      `o$     ߎ @@@@@.vI@@@@@$(     ]$$     #H|;b7P@@@@@H|%I@@@@@Gvn     v K8      @!@@@@@ m$q@@@@@!@B     %@.I     8B !ާ5(k._jQөwo%:nB@@@@*6I+ V׎U7+gk$\(KSٽo X;E<矪6Mj) **Raf-w&椕zFT׌"\}>|r  @M?ljzf KvmF?)5u. ?6LO5= UWNդ(%es\<#K  $ 5-Z){l*jAZ5tVs)ڵ63̏GF{n%Q'؛&U=%ݙ\`]R)sҔ499Mޟo7ѕy$Ms-}i\zcD 8.]1͛s8A]y:%  3QVr +4nHzIk^WMӢF oEUy^}b53sVkUֶkgBY%I*.Ҿ`X"qa<]&}`v{Ҥ?N6\Gpe{|}:nMY5Ww,nm6,iJL E^Xʾ]W詜Mv}cɮҮ8_͘w~9^gm.O{=:8Fe%*˜_MyK5 ٴZ6LV "p =EzgcN9Nlض?fvH7Pb   (7m]ۙ?Rs?]Q1iojx=\RQjmeQFUB&;Cm-]Z|e&];\'C͙i]ۮ8r;/y;| !UѴe?aG"o^%j\X{~/^S|:?+5R.-ƖV yI:{_5uH/FBQ  T@*OzK?,Lz{.qViX'_qMW㲭GZ!`@SZHwG&S'Z8_`9Z8*kMjMV.|^+Na$5G y{O\t<&?/+  ϵcF߻1LQj{M~|B5(dw){a̸2ijZ?U79jN6M/i`-0C*MMR OwoѺ]E=Oթwo54[Zr)γ'[WTz-^vnmްJ9-oI#UHN,TEwԶy;;z;^1iqI% {sQ&]kso֟=uZQB;.Z/95zB/1mTjO{豋ʔ8'UZƭ@j{=Ҵnҙa>͛Tx@wD~+ `*ŸƁjXr>hz*0{2K8<;In: մoT >[f}yK>lӮ8[7+wu㣞?Ƣ߹߯Ф)SgRnqQ4cK_Q,;OrKk.Ҭjڳ'uV+9U{5w2._U=,  Pqߞ ZÚe޲=ޥ,JMS&|5$7ŵZ 7m}}E o_+>1>8*: o[4{pTH'^^]&fU4+n_\2^i>YOGzӕ&Om5jt_0_C./( Cv;۹]&>?hP2WJ6kjp'[%CMղz2 rq{mK}Y~ a[jf%"gzzVpj؈+~;8@'hƪSg~XvLԢYI=hnN* kPuA+9A}u C:)G t/Pnv5q |syM3ڶ~5yg+¶ĺKbJB'?NHNcHN9C3\iΧ|UE)qwVC޾vpܻ8jwAa/  BSRufjZ\ݲL(P6nq[E'SZ$d7]Gg>tqxl[x׸T` ?G6U mdu&uyMX5yIz֟E|^jUno^sؚ=Hm/Umn͔KɫFN|]A.RjpkyO&l+ڸ}[yGRW T;ſϤHϠ̀?CjUd39_ߨM\jִjuCn @g|Tw~HK?s_k[|{3z=y:]3"zQYwR__.\R}FzJy˛LeQtM=~&n@jHh3.դ3.ؕ'4x_|Oqg6xU8yqZsz>J=rWO`&{y۝l.;v'~is՛ ꟩Ǵzwqle4{Q*=DYy?$?/h^wyrnn(͎5%^?Zsg+s]9uֻqRZa%|; dv ! &맧@_qjxmMիV=կ=JnNЪvwx-D[Sw^CǖiP mߐķc}x?/٘n Iz[뱒 M⫅Sظ& mnk6/r|/2g_]VRڗX\E땱55ʭo&_o=)Wǔo醱o(M;hoka0Z)35 fߛwwSuipLD*,djbKI&KĹM]Z\’.>yLI濷ɊQ"Sú^u޺'jԌ֒ )59Oj{M;ea@)Km'׮.YM[v6JB#Nr9V*> FS4w|)X ռv4[ P_tF~Wuez"m9Jlꡖl iErxXEq\Xc'1@@ ;)e<򿥣==jﺊM˲ Y tms}6IMd]cC^?~ gnP2:B|޸`һti۬z v?4,뿍.%mI%{䚶hM`}Q fD-gkla&n;NoMy^QTI`=r;ag:C'f6 ˦q?=3jnuWVEeyYqzbͦ;Ꜭ{VKF; SJ8fH3q]Xz.P+Mmپ<8 oR'g|=_cmuc_^n_U-ogyWG9sXr˽n]w#}]K+&们hyVB$z\&/+  @@LБ >/l5OE諹+b㿙%]ޒjx{W8+v^չyߴ@<1̤F:u+O?=Su.)EOց HݿFNZeP ,Po]g9]qb ۻ`yES i ͵1k1&ο?NFv~La2d>~X!S@IDATf"^_m7]nt άZV?u8ި ˵C{Bj7>cy#痖^':o6eo9 Z ٕWJ6מһG}V@{>Д6G^`ՇWĎ6$+(I  @@ ʥs`~weSVaq{>ՌIG-R}^'ڵ=ӿpajݼG3uru gk̙oaGف#+]&ir\+_nD+H]U_~ZLF`;~uZch7 a}e Zfֳ@s׼W ǜ/8V3[gn+ ou7Pjj74Kc @i04zKm]{ *;]+&oP:2U'¿Dp|%yE@UOeZnqqˇ ׮l39g}aaYx]'?iqۡ}#*K%Ȧݮx{ԇME=lxUK:lMA@H0s#vz_GMYSniu+~5SohȩjW rTKѪELM (yFS;8.,;p|@@(Nژ+wc:5FϦiA ]Iŧ+Uqğ LS&bqaW  (ƷGf[pNi G ͿS޹_PZ tBbqcnn tQr{LA=9x 6QE OӨ^5| Jڠcqx p2{uo+\‚ZZvo%7V!B޹E+'Ů/tUcDe]OvyԪR9Z>-:˵V3@n}Y'8?3#Pïh?jkQ {P|1]_Q:穥^ڄIRj_|[-S!`OR8GzR&jܬeS5{ŒTDmupN۵o$5tN9x5 i|Q&Y_識\#s3ӏuY/Հ_Z5XWz4hmqPVW=||ϛ>V3?,}Lor%8^0mRA~yڴq6|U۶:[o_2lmj-y px]Q o s*KYzο}qoPޞ>Ib>?{Nvl׃|+ʆ{iTojѸC ,ػ?8}vűV*[⽿|?˹%jg'ovGٕ0@)/sŽ;ߧӰEg]q?;?a4~HzV'|}oo3eUfDEX_q_%:}{c c-xG TsU3 xy>?Wv,Pp@{CΡs ⟣XqQHH2  p 233M*u>l^[y{T^~ eOPW.vzNvT\{kfY6CK/]c*=9Yxv 5EUϞyҷk3M1ͧ)/o6~]9_ ]]qBc<3dpwZs?~5L–aFϟKum>OPwAmqJ:Ի# Dt;J F! MX[Mij-qU +NhL@';@@`T(@@+uS[̵' ,u|6@f Lx@@@@@+@z6@8v=&Ү8qYa((  pX Ti&RX@8Rr 1*rIe^ fjWl   n@!@@@@@$     ߎ @@@@@.vI@@@@@$(     ]$$     #H|;b7P@@@@@H|%I@@@@@Gvn     v K8      @!@@@@@ m$q@@@@@!@B     %@.I     8Bķ#v@@@@@Kķ]A@@@@poG      `o$     ߎ @@@@@.vI@@@@@$(     ]$$     #H|;b7P@@@@@H|%I@@@@@Gvn     v K8      @!@@@@@ m$q@@@@@!@B     %@.I     8Bķ#v@@@@@Kķ]A@@@@poG      `o$     ߎ @@@@@.vI@@@@@$(     ]$$     #H|;b7P@@@@@H|%I@@@@@Gvn     v K8      @!@@@@@ m$q@@@@@!@B     %@.I     8Bw޽{u9ޜ%Yyg*AC   .|oͿ'4SզI-TXY ǝ9iQ Hg^Nu(2!s4B_iX4,٦F3֧5u{4oN1ޡwSSN[yŘ:mN5>{_S Ʃ_g6Pk7Zte85aM8Jj6,ʦ! *-k')?JVsrk4VnC9—t^,^[jn%t69Iok=:oOwMjX5o}#ui/]۫6MWcZYu71m 6հ>8\7mNјދ4@qf^j_P^w|z)gL%rW7ϬP+'iԾnq/X=ǰř7ks R͚;zHԋo5aE#wHy!VV  kָҊ_նu!Zj3OIzع];x\/]uf~Ǖ_Fryez"N~ Ue%}^}b53O$ o ;{J~x/Md[^XNh6ʓREEJdU,k@QNUӐ |ﯟ@6e48I H2FOz+{SԶZSKzm[M?yxl;@@7oS2ս}]ʾ]Wrignov}cɮSծ8_͘w~9^gmӻ^/.FT^`Ыi&q$7]QIO;O74e%}nذY{M[y%%+=aaiۿ\^w6蔳F juݚjX.L wiߠ~DDžif̺ʹߪdX9euN`_MyK5 oڤZvPˆɪ\61ڣfBӻS[6qr$<,md@@@6g5Lb OkMK̈#spZeLһv%}93Qɮ8r;/y;| %.Z90nyS<rvYI`P8ރ6Ymv&?Kꮱd!Jך6Pěabһ2q"z~y7f> E_mJ8KG[%`\6!  ֯ȼ>OuJ Ӯ{[=_9}7WdzVC3㮟k/gN)ɵ^J+PI3-]մ~\w6oX[XDhv^:ɹtltY٠e3jZd%s:ꨣ|_6]=h==o.fO8MnkKZ Kfjφ|7O{h ޓP0FH JKڵ6eq|~ޭՂ*~ێAZ%!;ןgXgVZ#@Q497 V(;7޼oG8+*Uꦇ:㮣((Jun--u6GmѢ}?{MMOwgg3 $Y!юEoș="zID9>(8`j-^7+?$ sf6e e#㞜&Z?Ž{-T6r"@ DE {oӝsQ' ҼREDWBa;{C}t]q8\(_Ѧ>-gMP .muZm+SIhRNg2e6Sq<-X φ. m21 On^ P7q৥0c9;ZGUA3[x7lJ,vV?2lǿbLb8ZpRMHȬ}o&^8jEx1` b„M7mbl(ˣu#Lyl5XYEZK\5+z~{n^weQ=йT@!-ӮARfl9sڶ@j"6:|*pӒpiFw:>mǴbӉY ~g~$Y1STaς0pe+>C"iR8\٘@{9jz)T5Z3av6w>ig^`eֈ:s~WX=zqXDVxT5zRoW!19" bl`wɪ4_v5< D"@%·~#r`W0qS#M1V(~S-MPA)BD>2s*G l2aWOaO4K'? q!gǵ/Gj#:\ FfI~<.Pd'[0 l2{$9]ꇿ~ppj g&M\2UYe6$Ao_5C~ e`_3U U3 J[2#>Qs[ otTE܃O-wNk AZwI|Rf<ջ +Beܦ:3$z KofubLxZ^;v@tZ|4fET~Ap։x 0v! 8iCi\xҎܴZcs!L|wncq10kVjFA0y;:Vb.L.e)'# Pn9>.n}wL_ƒȮa[w)>|$|Pɲaoø`5U{(^=^r?V&ch#l2Ok)xj;A* D"@@kʱ"X!Q0(PZl0ʼn1>cA*#5IO|ȟ??ry#9tL_BB눢VWM%/c)AQ(bڿ4*?w Jr-Qu$D/`֑r)φ<%9MC@|ݞaLː !:~4n Fe!K['V ۋ9GŠ}1L'b[@s%Z=za /뢰ʦB[hP{aο\Y!m݃8.wMz:}K_yݤa_m#z'!YriPP!c|>S9ߛ><﹙GC.^?gcgKG^!g%޿?~͔]%S* |˜nNaۛiY j~6/3g^l{2fjG_PVYnI^/$0~+Br'lah3{(im7{P /"@ D de5)d*wK ܺ'|TĒصn87>fPQj&-E#,1աKx,C!|ۆ?Na´U:3hb<5\蒃X2c[$Y&Ź3Ma@{^GZGaU̓i=)zcCAGJ,zٌn]&J-%?yEGvUI8u:vGrfIvvEoZ>A~'huxh#D"@e-kn# FѢ6Qӷ ,|R!Tb|!xMĀw#ߌ+ h9k$xن~C$[H=ܱG/i2Md*3ztSގjk| ݙn"?Zw bYr$53-,L|C)6[%\نX1OGCR<.eDuF ŌDC^6f*wLlK%N֎@wD]a=/P1'Y7lmkkndDr@ v.w^)iYeǸxկ,5{.XOc;ƩMއ;7`I?ytDx+ߘ~ۤTNyG%_KoeMo-k|S볘Exg*=BqiX3 6C3: qBL;|0F^ENW(UvCK>A 'hqT D"@2M@^t(زi,z[3HLv6ku/СNݎ%qrdzLq8m fwGByŨ[9\op ¤qf-@X02LeWwUx=m@ywD]>Mܿ5d}_.xc"s=cۓBnM]52 G0n&ua8 -Znᜬ㭐;+Rf=̲߫,/w${0 f,vLy 2wK. D"@Mk]|7Y:wmݹtG̙<KIL%`9̩,͊} ^MX }W~鼸wfr EڭwZQ{P Z/eM D"yOT9?)3*VI1xK)ˑVyǴwx_™b Ϳ,U4:bUk*޿92/?MXD6N~OUE q΢Сvg|u[Gcpxl6rXE.dg5z.S"{iʠTPuxp|b%PL!!cl[JA}[ V`eM>dw"?9gE\ݛ(ep?@`@o: U6چwX@:FtTtFȨKy]Nq~(9B;l)yK)E6v0Q³tlv&Ǣs&_epE֎Qli\dw;&?' ڔ;JHB霙i0-TF7X"Yz-GVI߯"Sn5R: tr6鰐*>ڽ5R:"D"@,':;3GM)26u~6(>unEKokJa0ˣ]Z'wʥDaSL;9|rE(JW M5Ax;o"wWZ7l,^6[jR2sNϑR$rҮv%1~D' `풂ұ+ǿ>*GVI/y7I~Ryy3q(on7sɊ _B"@ DC@V pJ ӚX>@ݗה)LcvڏCdqЬ/^+6-=9+ ;d6uGf'٭reg(^Rӫ#wd ,N/>6 º ⡰g2L:u21;? QQ30wc䤀?qSʎ +a'oڙbFE)V313&,H5ċ/Vhc#,OӧO;*L%?/+۬qE˝Qj\mqNl4Z79Zt3ֹ u-L̞+wܼ2;VFě3Fnd'7if7٦`D"@ w tTC% _9T*bQӈeN faByPz,܂@=+F*|dgj?e<)vq~g?&֧FԐ3(,_[0z>Y"4'JMG`H Z {1ݻy6~hذ: {eO~Umx6ě5}<+ HZ:A̚Hmnhכnrx~z.xZ<,ތlAJqәԝVu>byYL">R5GFx|.ͩ-p9[7>.-W9>ʫv2r= ~lϠda.#F#pn¸}h<'jZMxx1sa |JtԆZPR:|W;&(BEhx]_}`^Kɞ^-B:O! 2ơE\W 1~yyn RSyr \<:O{>e@77H)_z'#Tlߡ&D_>:cС#8uӄbϢ\h%b (gWK+~ QXk'1,~1ڞS0ՙFe_e24B>џĪm1j*ջFۈVʘ-F{3N_d7"\\٢r.^»x*רdye¯- 혘'-}>~Hfm\~s}ԩgtҙhLۥq[B cߚ'x]'U5NQ߳ }\y*ڝX{socsѩy>e;8je E?tOkV_Z!D"@u&IdDT?3w1CyG.XP,Yy ^.T珐-?gO(UPZ 5,2W1S;+P_4" yyj3W@Q0TtKPx<-2:K#N (P6>ϝy o|$HtlȭQdN·Xe4m_Y{.rjΰTZu@i cb ^'t|W5sgPBu|(9k6j[gmSe!\gywWry;_p-ͮ(CmѠ >AIÓ.y|KCQ ^@='uTRÖȡMUQ-x.īzڎh/(PX'iP1~c0`lCц*!1bfs3^C.s94W'rR}vWe06HFFC'2ʚG]U^GU[FW۝y -p`Z A D"aR;$ړdo#W&p;'e8x1'! 7O30f8?~DS~I1\LH|Q0}̙ )Gʡƕ=E.H^*^c:mnLkP^n-5tmӰZ|κU냦5B !_ј%nCn&l߈Gpq,$-1{m=LmSBcx6/t7bYf$̟33B:Sn^9'cCv;Mv!li1vYNsGsct:wK2-5+KK?l9>g{ݿ`Ϭ |q$ Ý ۹kzwYJ4ZqZ LI"@ D·' D"@ D"@ D d"@ݭ:@IDAT D"@ D"@r4s"@ D"@ D"@@F Qb"@ D"@ D"@r4s"@ D"@ D"@@F Qb"@ D"@ D"@r4s"@ D"@ D"@@F Qb"@ D"@ D"@r4s"@ D"@ D"@@F Qb"@ D"@ D"@r4s"@ D"@ D"@@F Qb"@ D"@ D"@r4s"@ D"@ D"@@F QbdzPl]"@ D"@ D<} O_/Qa|GGD M#Q5 +?@ϹJ9$ɴ0cx2t[Gv>9$s@hfxJ*^ r_ o_Ɖ?ņջв(Z$<Θ[7o]WƐ+vx!8ac{njB6N++hgwt5a "@ D"@L@zX)lu'#ɤc2`Δ ~쉉ҍTarE,Y_߀R_*K%5\U}$?uQś I]w}IzrB=j scjBW̏_V W[>7O'"j "Azm$m0-$Ab´ñ]x׀EУ7{gf0|'nNA{@#>i!(# o\Ú]d"@ D"!P0? w.8<PAP }:UP^)~._y[#z(EbE^ؔͯido{^1SY;V\; t^@M FLR (&.C`*]Q߻gU;M~Q\pڌ4cQkw[1"wƕ˹7O}zRh6Nj'58r#e)%S-':M D"@op<̆EL4j6ڑWV#&G gu:A1&2 pvI4~ɩHMk7 N$@8ux/n\+?oyf EeKf9Jsb-i,^b-A3^p&-I0oi*?选vP&>K/w.0)f,FW=.72ۈD[/v6A{+GB"@ D"@J ߘx4 6[ᕲ%?}7jv9*}_" F!U?`dUYK@۷pA|hx>jz6xjrķHeaY):WbS3 ɮx%PhSrWBf}GUXn(aߔ/ETa ,ƈ#,ZlْpwSz~6*Te;o0;0 |mWy7 5Vpۡt|49˲+ސ%:j/˧E D"@ Qⷵ㮈i| پG9JhzdS򇻓وUQ$tɈǵ#/طIxuR\D̲Mfq6,i K`yIf,?q68O=S~@|<3EpdbOW4Q,?RZB (P𼟰`q{9=w`l Ny{#Һ;j.|$/zUM3?a4x+S`DΞ֘#᣾DM uiqs>fVLqZ2.hi.DOwE)HJ{<C}t]1 M#<=M1s".櫿-C\6e!,bbRolCv㝆OX]K,qR/F Sރrɉmbl(W,XDFe+}]Hg@SlQBڈ@&Dr(Nfrdg1d:]s Mx]=hm9hb5ŭHfxU?2;e-7M> }Dwq/ФĊN{ep2q*=>&Ys:lY /'qt7- G D"@ O7M"yyrbNG1tYh^ֱkfYR)%D!>Wuq΅hTlo.Jne,"%O)gER.XTB 1Gf}aTi?{K~'g%Q՟_oT&o_5Q}!;]nBdqf/33);J&zQP+qciGmӢxs><yYHoN*tA+"(H &!-5r_9 oɨGegpDaa6e|P>?60){Ց>=Mv<8ߗ@.%Yf%m&dv̍/$<+GGw Y!v=Qr93_|[t|/>Onl=X'ŒϣCӍu"ۼl1 D"@ Dw %wW V-uj oB'Ŕr6dsvKa ¤yEIoED8 6̈6jV$43A3- #`[~4 ʢwʙe$|[Y|Kfʭ7&#A&uWҡzQpSx+'.A _Y%kQjхU"?3hlLt^rpKl &b6x:߫Rc 6Er'x #̔k{QzJk):)yr6C8P%7 D"@ DN@w):$5lgxSas2ZmNƾmeA֥^C!f:8O\qnOTT_Do_bvНQ]gdCY!_Q#sB(A ћG^FTaP]KGfskY >zp8_Vo W!F,??7`!ǸxfVHc7`;f_:xu㶬Y싪" 2lԻM hQ̏,Qy' s:a{Bt?u[?[HYXWYWw@)geCgXnKc0bi X 7e6)@o6smbx6ϊ#^fjxo| KN|-ѩ;fߤ} 21\d8k|jguCGȼ[W GeWv6y`?84HK~cو@5*YA qD+Wg,R-@&F57g·`_5N; XL>#&;6r[,X =hcNY-˛&Ȣ5+ijo8CY~ \>߰\^[fz5+ K³v6n&VJj7/+K:̡(zyD'oϓ|\D@M';U32"qk^5hS=FCel1u"TWmE;z YW-K/5z![.Elm[GE؏}ΊRʧ)‹o _}χTɍዅ]Qґ|&_oW^D"@ D"@Ȱb\aNQۦ7X{CkqM|]'F% v¡l%Cg)Qhsћ'*jiVS:UT!Bl^~fVe@E+bF]َ9OIev WO= j/켲'۔z_?2s;SصE2?םW;.@d>vIr =Y:]Zm:Ar7̮c}bv-ףe5@:_)(0bdnޖḪKEGh93;#K⭉}1!. .Ǜ!KcuO>݂#kw #j6(ךbkbHtS:}k UQ *Ӽ sXj,{fɓ"@ D"@n|=_#{`x$Eo\)$ ƛlU,\ 13؄e_ ߂6R&zGXJƬyA f0 <4{<6N2c(nGt@Ĕo=R}dÈ6EtF=_+Z#Wǯӿ©?fVTlMeKw*3sk? YЪ\!%iyt6fh#ILX_7âf |ztWQ%{$8b{:c@V \{?mKSa<،I ^bе{}-,λJW 3Mw:$v аv̇gk% NuNŮkY;\Q\#D"@ D"ao06f#뙨%rSays\J󅯗 ,8:? F`VV 8 t[|j1"-.^`ߗHSLi ޛ7jAQ+pzzh;"273`V?ZǦrWcix `YhB@:ykiUF,0KY3LD ~lHq3:hދdk?gI 3 h6i g%ncXXQC}P`3-jYT)ky~]= ZJ`K{ %쎃&(N ~?Wbns'&>r@x_C0}gL!HtL D"@ D@A@Wxr_ŪeJ3*܃=*L(NX^'o"9]S/| (c+0x LPG|OTELĉ!tS;[^g`1{HGf'mjp~f5#s$rV?ZGk~_ gz>6$I/: }ʚrbϕr+^Ts Mn"q!hѨ75`mEok/I3p@BR.';?xYS<'i/!?q)qM8 {ȣyvbz#Ѵڃ ,-/|7[2Fm0-Tf,7X¶"@ D"@SѺS س|_h`EKR"̞͋-S[col cR3.W8.'$H'R eР|,a͏߃dh7TfKWf !y|ՊVGh N&M2bPhTV~ftvVːa1 qrϑ'Њ=!qncHm;'mM~,֟7琷\yp S6 gfx)p]$P_<6tΤ3[c[3>yq_ CXD%or"@ D"@pI/b[Op,ƙ?6BkZ`l2Υ˦K:%f}Z9\fAWrjq6{ P)܅ Ҩl?4Xq6sN(^3::3&,xEYTYI37 OO]s2TZtgcS/`èU| Lcl1͕eh"*FQҵ8>yӮ#í [)De&p\lܹ[`rhKeU~߻Νva#E`CDnuN%KA D"@ Dy$u*eN(lrisו$r K\Ǟ#>3^2^t_< [.\-9Y^G  5J*"f~lzrUv`z176*Xj7R~ĬtY|8T:ђG8iB>9>`qlb,Jc2a\|}EEiN*Z8cXy u?1᷿-аa3t4 {R\D@&V rKI8k(#m'/?B NQu 5]r;wP/a7/Q%AcәxSΣ?2bnG<sItс4/񲴰%3V̜3)O"@ D"@[-10f-ɾt{Џ p#,Ah6L|9ʧGmv؁80 @a*ZE'++=ܵ@L~1fy5nٲޑhˇxj8o=>:S7M,,ʅVB)f_[⚀Υ}ϧ8O",2o JnEHJ st*3[lKyCaW._ū7p]xAP,+xݿEGE)CiYU~,wմn9GGYi4Y&m\E=UiV9V\WrX| QzcyCf3Se *Zf\y Qf3rfġ)fxy9567jdxuPJTYuo᣸x:yWG:aQYh}9N禈VlV^0zF?-}a]![;~K3fj;|Vx'yEi+r5؟]h6(;8mFh1V[)j!rl$ W :zP?j- }Q+٧|':2r+~&Mw-VMWVۋ>LtQU8lʄ ׶UQ2q~OTKZ-,U*\2SKq,‘WbTbZK; wa Ju|(9k6j[1G]){gq핯D;X̤֍0 OP؊uu ׎E_5jn[,yʠy$'ߑ7tt ]ʈ撏ґOq9$Es"tr}բ"0szGLOy2 D"@ DI-5dÚ}&K2lrjXh&s3 lqx@!Qr6Eyq\>/LIqP%n15r7e2mv19-,%?{ 7O30\1J>wFYq%QG037+zph"n< $:թ$c^Ę*5RH#L%gOwFfeѳtloL~}űkZ?)fo7._3AD7^ds&6Á8(t׏ví?Din_8'f8֍𙻝f3kvwn? Yz~4]wE(.~>xwp734;E;-|Hw#&G D"@  yxY61Dxv6d#4שg$^ $3p2kh~]Tóc[=ÍlwwҮӮ`S,~ Gc_.f]z)[y̌%X9?EbN^rR-9kQ9-u^~1nQK 펹[ټ<ff`$vkO_ES޼_66#żuqX<2NG(2 D"@ D"@ D4$|;B!D"@ D"@ D>L D"@ D"@ 9 9P~ D"@ D"@ D#$|{""@ D"@ D"@@N#@wN#"@ D"@ D"@ D"@ D"@ D""@ D"@ D"@<"@·G(2 D"@ D"@ D4$|;B!D"@ D"@ D>L D"@ D"@ 9 9P~ D"@ D"@ D#$|{""@ D"@ D"@@N#`i~kw*k~tv^$fl O(C;']2^`1|ؿ^D( D"@ D"/!9[?FH2T2 Й0/=1X*LN>UtNӟ7?)\.%Ss}s !5Y}>5z}FbstD D"@ Dk /|3@z炯bA */Emi,NA-;=R*l&|Xy[#z xz Δ7Oň@y=zw.w@Ϫ~,mMWuߜǡm&K D"@ DL-K* Fٟ,K'WV#{+Rl` N"(fv,d8a@h8~U ;Vao]Ӳj9U~t@ D"@ D; %|+vS^bWǨh|2eaT~t>(:Ht?*$D+;n[J##| 7WG$D"@ D"@2,|yUeqUHuLD"@@O.yg| ;):BP@."@ D"@ D_I }n뷏"q4,,-̞;CC N}8}JmE8VE|>%'#>>.ƎX o+Ayn]!E,PvPT d|2OSG6 ti>x.ԟArr<~zVEtK]|p>nhPU=s(<&YdTx+t6\5f)( wZM0$D"@ D"@8̈́o^KR¢iQ٦>seѦ>DyӥZlŽozEi9lz +!t`3w ~Z: 36ާ_tBT4b3goRxkrLDp ,.wĀk ^t6u&$8aN25:֯|M7iH bٵxfc1oc+y ʒ? ΍OwE)HJ{]s1;8L߮:0/b a}x{3#fO}W!gvk=]ZZJXz~2|/,΍7؄C" s GeZΜ&J[vj"6f[M)"@ D"@ D@I@9+3.'jKXfмc5PRK21݉]B|ԯ6ȭ;QMua(c-yJ9-"r]-蛍XT,Y3 J[2#>=*'.Lg|2|C8>t2_8()DGz_ gfR\w3av͚V?\(XLܸE9 qp/xj#WGʹ^>(" ;*GeK-Ȅ{6eŐ?*4YFqU( - g"0wM1U:e=b"ֽKL.‰R[y2aqI69#V-3s׎=جO]3Wq'Q8NvIb>>u;ϰ/gUľ]y(~ϴ`!! y?e~!sW/VClV,_m$ĝ?118wYҼh<^Nuv߯ݼI]M9&lY^l9@gض5GC cԄCbGA4{Gfo4m tL D"@ DtM)7ebBşʲ)>zn#,l}۔a~K4S b²4&DEvLv_;cNu|WGFaȆLCeֈr)DoT&s{QJZb ŌDjF?",.[m8_ lEՖpebml&#AoSgp&Jc ;abNdAgB ^ ^9m:GtD EԦc-$&EO&]uH{8{BO*ߤ\׶WDr*$RvҞz: ܶugǥ%,vRa6U ,m[\;*R:q'F;opd>0&l؀/5Lcc]e~quxokO~~ugFdy:I?{_j왆V P(@ P<*rɯJtzqo3l6o\r >૙E˲̕ wz@y>b̒"\w/Q0q[c2V}VC:NÏUVˤrBeu k/%ePk+.AN^"&axq.8[C_[xnw[ XHMuJ6C{~8OJ&g׮E ;>pQϡ˳P/TL|"mGIT#ҡ&-U\MQ :w\F 'K_tc驙bپ (@ P(@ \%#N%‚ё"mi[GVty zg֮B`𵓇5ɥeY@)_aoSE{ET]-)r~Et$yi?Dˏ <+*zQ#[!b4TkheQ%ĝN_ ;Va-k'2Bed:ʼnbU)@dD^>u*V+RȻ^e>#e:D~qux9OO o?xF| eä!O y~ VXq(@ P(@CwU\ | HNs kE0ӜrÉȟpsZS9|eL^)#Z Y ݇ D^EYG2oZohWiMF aSѱQ0JXgʣaqtSNoD t=Iwy'0 V4}[wd0QsqOГZsA܃q{r 4H~G`) P(@ P pɗr29d؝7~KEJb^ǒ)6>X(l6,< SǔnMP/b6>Mie}G^Bۨu0EoU8/qsE[~`\~=7Lٷ 5O)Ґd_y )bPXy26jbԖR8 甎&_8)zݧ:g~u`ϗ;9x|ϻ|?V%7mZR\ߙ$͛㏯(@ P(k/[eR*(hDu73fC{{Ŧiwe`uBHaz5hM7/ϻη8ֶU~^2][zꢭq6bÐ1]΅q#Mlz)VNq_<ИЭOi"g:1@nx:|+'@nq IEϝXLjs]-"SNz)s(@ P(@ QBTJ{"P^@]fnjOѲ/IDAT魳zڋrp٠ƀCUkz2Ky8m Fג"܁^~h9T1/GK|e^Ġ":bn9T3Ϥ空6OhK5ĄZ#/5" ymGά~Wm{EtE nҙ`vMȐ^]*E0Ny]סW#*j&ƿ񂝣" W`$KO-Mʥ+4JÑm,q(@ P(@S@`rOjF/Dz&q19A܄D@zCs{%i7L:}q>xs6ԋz%kNr"l> 6ZaעYwCmD݄Mm !'GӲwjc5]d\YCpÒKo+j)WEiKm1X@ٖ{R(hr}ˠW k2[5PIĪU)ڬC:%M)|p]EoGVw]<_l-ۢs9zp%+qVj į$?ɪ^f5|bZ(@ P(@ PxC206jl &7?}9T.-fV i#Q#2ſ^/}'8+:(Reˢlq>aEl"7#PrKPVWv~G<H#3.ڰ?4G||'T !5렪ȯm*C*qZp1oY%p`{_y?vMpvɢ(@ P(@ r4ﭖz@*r;*е~=6 tmmv3 ]z;zeί B?yBus>l7n,R^d iH mQi֢ Onb*w"7#pAuѦk]ER^suvtRSlj5y "qC7ZvyF;KwcB+<CE~f7Ǽ|(@ P(@ P!p*|ߒZZ3% [v61i1 |)o\xd "nqu"Ӵ]fuT*Hr-\9w1K69Ih0E TJ1~\"x>MZ EƁEb]u 26c]f͗1K q` 6Ǒ Cu@ WJUaͧxZN$RNcɰ\MCZMKX65-pW=uY/> Bsβzr1p)P ֭9BitiHxM|(@ P(@ hz t$(@ P(@ PNB(@ P(@ P(@ <Lup'(@ P(@ P`I((@ P(@ P(@C<(@ P(@ P(NB(@ P(@ P(@ < |?牭(@ P(@ P(@'v(@ P(@ P(@ P`8Ol%(@ P(@ P(@ 8)P,F P(@ P(@ PhgZ_`+ճ*dz~3;b d6":Q:6OK<νOPU û՛׭ Z{r;៼Po(yFG"ܶz P(@ P=-\6FXthҥ!-5ag ,^H^vZZ T *:Z^60\ (-KZ ow麿/F DD}k"@-mNv"İy(Vxv@GVDw_u2N\k|Iszxu/!R(@ P@o*QޅkEP8$+TEg_[18ʐƗ^5O>XK( 4i ǢWN/(/|{F.E~|_y%'ĭNכׂ=mV6}EHGS ސrhs3dnƠ7C‚NliVH(@ P(@%ԿZ\fjq_acJUo?pe} bCI(@ZN` z7Mm+Ţ5{ya{c5jg J{F֤]ÉB>7}Qٴ$zP(@ P Soe2M4A3SO}液=V8㼀ԟy(ഀAoTUQL316NGۡ;YPڣV!KIJ7:"b@'̎jZA'Y>x*tbz̶(@ P(@ P Z+0VݟVCZYs(O 4jsW |X/ l)o(-jT J{ %Qt\?#4w~6_xFR[ۢp|lP-(@ P(@ Ts|;gB Gв1_o@hv&wͦM(s8PA8{2|JOGrr2.ۏmqy''ZRntQSƝsY)|NMSU+=ɸr,og?VZ(Q5\!MDOX_Om 5%'|]7Agu-޹zKVrG=qESvl Vl;'nDgZ7"{*`Cm̝MA~G}mݍ J{UGqS yMl,ΦuS"﷾pVoX;Hypq=z; (@ P(@ PA --@i1@xe"7qq>k?/ TXJ!v|Khr)yʠ{sxυXc.1S{O ͋~ׇ_A>+4ȿ_TQ~5mUG:/wa8" */T76UzRR "f}렴4ׇӑr~ƊHc$k4b(V'抋>)@WX}y.a^gcHP\1%1 bo5pr|ڷ2?jvioyV∈X=Ë6tW=rbMסTgΗUKw\'F^Ce+Y G@1̔C1RMOu2mwmEon^,H{> J'F(m~{Z~Y# Xdq@m)@ P(@ PQ-STNu"x9cn?kPUK,ǰ6>ZҼʻ+G]|LjSƩ5\,`qU0 =*֫Kɮ㱫r4LD~@LA}`9&o(>[܇9`L_݃WG3/zpKNVUcp]Q].܁~ s-n[?x{KB(Y9|~]ǩg[mϕT'ZEDa[$(Z2PEOIMbz*6%E- g!p#ɖf]w\=KQ,l zWTmm ? ZK9I{峞$2|8<.ֶ2Ҡn^,HQߓG.R|?WeL7mӒ=,^m/Xq(@ P "NҠ]5 y=o\pW}\ė-l& VzN߇{K~7oʻD[-^3mKꉼ]SXV{vgD:Uaہ?3 ɚ'P28JbYh:ij> rZ^B֙# 8z"n$^ۛq{/cli(HMsno>UcMD~ٻ~,5{yv6Pkfoc悖w3;[޵J@dl(&UOV s2WickEfP!o) Y@u'CL}[]Ce7,\(Hl gea'}LngCS!| rY(@ P(`{rPqL97e*wRV o ńdƢ'4^?=m ǝQԐdK33-( |Wv7x.Pf܈>~h K= 7Jtwܐ'UMoOnZ&Ux즋7톈jw=n^o)Ké svaYesIxRpl7+'z:+Gzw}i<#|e5^_ofu_~3eM~^Z_n<|抴:6eFwy( F%:t2RmZV܋ۊTT}2߳V9kKm0 +VP_~+O74mŀlޙUŐk֯[ʹxq9*>Y>Jq$2 q}|>yq,泚_#7" y)]:?)| D=+JW9>|J ~.A֩~Uzt8/V _eSZ̐Dn a }:m?eu[[ìXMQJ9SzGlP@1ߘRR:1c7$o di{qvYAkϖqP5`:;(땖4Y?_ʽ^9 a ?_ P(@ Pp9T`̚BPbUThr {Jn>#A\DMʉbG׋y;\+ dB ^ ^9m-GtD EԦc-$&EO&]uH{8{BO*ߤ\׶WDr*$RvҞz: ܶugǥ%,vRa6U1pD^tکUW2ԉ;1Ag~#]%UwĬ1pf=f~}e*SCϝ/{{$^s <#0b I*Y|cUVlϗ6] K:csƛj hUɣďvX(@ P(O ypj^[gČ8ۄWϽj~l,se3Mlz@+Ie}*už%EaS7,(t8-mױB>!*eR9U?jV2X9ZpY zuZ70pAVjfzĮa]Dཱྀmri}PRx0Cg^IL|"mGIF:t(IKU2WhST,{==z2QCwIRx~*?tXdzjXomfʊ8]c>?M_S *K2KXc(3oם19Nz/ P(@ P@p9m02/T.,)жAou=mIGǠwf(_;y^\Z DQ:XWZOQmr(ZnWsMhwEUEot8*>}2DL]㰍XZYlTw q'S~+a*7@eS1J)ZKI6S(VeM޾OJS 2O`"ՊUY3YOw]<_ُ@lķPX6L'Xiu?N {j\^' Z{lCt~qDrWL 0WcrYb(@ P(@ (ߩWq)5 9 %Ls '"zX*ƠX(l6,< SǔnMP/b6>Mie}G^Bۨu0Eo 0z:-4KusGt\QA7ƣg֨_"$:)WhCtϨ< SmG S4$׻cB:~޺ G}{Z03B9Ij^zs:_.=_ayO#*1i"%l iw[Akc0yHZe52yxrxtD3݉(@ P(P\浗2EJHK s^4n:r G!=|bӴNX20Yώދ:NOq!E0`&ϛ][uk[K}?/tXU[juz^W[ܐ1]΅qCm̯''mߥ@X9}tCcB>Ƿm#%A/9E'@[!}JAo;0jU*WXd.h1>#̷|G=eQ^mO<+[nڴWNS(@ P) GYkT)}Ϝ=[(PY,3t8Oo;/0^u0,*_ ?'Xi[0QLCˡ4mH| ly\.fEtĤr0_s/O岑F~"@[!&2ML v zt>~Z- 9-:m{L(z+aw {kB,Gꊏ݈rzw]>_=? 91p"P'v|&ROȶYrpzt{rjuA l `KO-}Rx?$Q̧Tyxs6ԋz%kNr"l> 6ZaעYw4- Xyi7'BN惧e/"ޱ驭k2?%$ f Zwb}m%.VR̋BmSEf F",x.=9rd2lnLa )ajbGU6됤NI}j,\W*6~:]ס'Gk~=ۣe˶j@\I l\oZ{,O׶~?{|6W^gO']mF`+Ɣ&үLzE;Cr[&Ar>;3=s!(@ P(@ PƮ[9[ @-pE_KKAc٠Bm)cihxԺƈ&L c_?%JTٲ(`|oX%I{R(c%(q;#ǾMU,y#-~L96쏃1I:UBH:*kʦоs!\==v+eZ Fw:bLC69cm* Kx2]. n}dznsqiշC,gmRJal!֦W1T|Z&6v=WBLkɿFs_x`܎{Wzj|cu8_ Wց\rw\:_&J]䀧7n|^S -wY'V.h'K?.]-׳ړ}Ώf6RyU9阽G'F3(%棭CD]cƶw!TF!߇ [גt+=q۬օ0vL̍>H1"WvazŌ^0͗D?p-kQԿ͋]{75Fuh{.mTJ 7LBs\[Q:]#D! K]tw\<_cw3,-W%Ӏ:N<ڽg$Gp-av%=;Mqd@n3H6~5,R%iYՃ[9 s%(@ P(@ Uhhj.|f ]v&cϚ>Ts-B"iy%}Ej8mvS+Z7@P-•sGdYm?렪M D(mVe&yiX \T(ڔ|4/b92k}_]x[m9f:jWP k>.sr&j:i,63~~HKs kwB=i_wc^>_5ӉP/TI0w,''Q5 kݚ#4?+JGms7׳;㊓.l <7w[ٮlβ(@ P(@B 69(R^SHwՓCS(@ P(@ PSN;0\0i@NwBY(@ P(@ PߏiAQ(@ P(@ Px|~|=܍]X(@ P(@ P:(.LdA'R`)r}R%=GZ(@ P(@ P(hppG<((@ P(@ P(@ P$T'(@ P(@ P(@ P`:< P(@ P(@ P`(@ P(@ P(@ .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`-ٰ9ஸۈKedTbџL $ZBYBr-pISYLA,0thR` v}qOrǙ'L|po~P2[-O|zI"e1w5w菖r(ւ.c'z` X+vJGgSL-J-}alb|iy9y7+gH1"ڎc9ٻ [[lFWrVitcN<1MgNjJDrt;@*pUh}aqA"@ H3`A6<K@1(P `/8zpUnp^􀗠 !AH a ڈbX#+ aH$#i H)Dv 5ȯ r@!]H/b(@P3tdr 9,$ȧCJJJJJU%U3U?UB*wTjjjjej.P'ԋwWf` cXŸ jkhdhjhTל9GJf'c1CYU#OcY1ckbkJi^]Hױҙ3[gc=rǖ=2.k;Own^^&z}L}~z /z34Y>,Vokl(1af8ddnkTht18xqqd&&ML]MM77377[fVo\<ļ"ע%2rejdnUeuvXoG6N8zMMM-6̶жxI׌o.n{uIoU7i_Ou]'deNMN_]E{]L\]6qpt-sFpu[v{?qnO#ON/WvNoCow1~c畯{?w~g1 ؀ʀǁFiANAC ԄOr`Rs(54:2IU(q2:yu #@DHĺG摹M!NR5Y}hF}у11bZJbTĽ_ߙ0>aADDAbC)).iwԀLsVR:#8c[̈=YY'La,YsfuXtnvtqC 1800 2880 1 ˔>@IDATx |TՙA LD(DJE\[ZJK7[0KKKJKaW@(*P%ւ"A$F#m ?N{$w>0sމM:tpQlŋҤI6@@@@@@@@@@@! 4uߌN ։4@@@@@@@@@@haI 6+@@@@@@@@@@0"&j4@@@@@@@@@@h^xa1w          @#D]}@@@@@@@@@@@:8nI&~y~     T@QQQi0..ξ6     @qJ$ @@@@@@@@@@@~>?]b{     @M 6aG9     PW3nY#g          T@I˸+ϳ          u_ D|\VG0          TR=l@@@@@@@@@@ Dl`@@@@@@@@@@ J"xB{Q?4@@@@@@@@@@$b~RCd          TFD\E@@@@@@@@@@)@q|n@@@@@@@@@@* 8/^D D@@@@@@@@@@!L"&MD9a@@@@@@@@@@oaI"o"          P9$xtZ@@@@@@@@@@$P$t@@@@@@@@@@*.1'          Mi&Mk98s{           PO"V"'kg           PWa&   PIA3d^9_/^ yO >) )Vk@ eecQ~G~W([_J() "g dy[:Κ @@@@@&0Q?(IZ$ol"lө͸ѲhѣL]Gq3/  KyZ.E@.@|Jzy="{d֌y⻜KN).ҧGFNW..=A]Gɖ\L'F]zJB:2Jjq5Gb$S2tf:    @6S@X_ʰ~˰GenŌ:ˁS(&Q[5)骋Ʃ}Aqm= IU}&MJ@@WJoI41K<[ko`jqyyT SNNK@l-җ# ~22R1G|y@}uf&    @HWU` ͝$>U3{N'1IZ *CrEj =$  xN! TH@WZIYlTRiܯd{e W䅻/ZNy_ Y V 7֯5ΏO$KP,$׫dQ2>F@@@@8./WeT`oGqxCԍ@hF%&E#@@)PڧX$k2W+þL <[UI' gNyz9JoO$SKכT2kDN! >s>@@@@@2kW]<NJkmr s  @$   *pLN%:g-O7JzŇ|ڹ[DFXp;j^jo=ϐ,A@@@@@(KfK('9t s  @EH"}@@ L*Z)R$-cO)kiRgL\%U^\ nL6V$%HXzP>`*+r~Th$]/["i_ sg.iCdG%M'ȒNe$\~=K*YUbed϶f'u>O)2Ѿ)Q[[&Slnd2NU6BWdXZWbBKYhFoH.^R]7k0[pc-)gˆI2'$v0qd,>xD߻J&nPߍ_Jc2dC}%AJԫ<~JUZ-XO[?9J];xw ƒ,RCd1\C#.Ajm{s"CwasKO)7Z}@qojՂ߿azN޾Yr ]n"   }[UՌdZK}XOkKv[T n뫙2}(3VcYTTšͷN!p̟)0H(,ξ\k̴VX~~̘0SD]'(Imh=z6GrwI%U,q[k1y=}db ֯&?0PXJd-)Be*n +U4 U-hƼ)}aߎ^u48U&N[`$u<;DЃKVdXB9fyq.=@@4I4i"rs:~M8  hzud&feڈm*()9|)lD*#ϩfv.:8%:pLS fKS w'쟁B٘9Enue]p뚾}d??OڏOddG֙'˜dLk rW SW"◍3FܝƆwp,R=8{j-CmkqH\c-wß\^ 9Q27u,Jv_<5etLY4w^C ĩgkK2w( Ȍ$7yn7(s}ԳZȨrF@@.**'N  И毕f2M|[vlu- ~Sdie6IMj7IfV+&29}U|V#LY-l!G\8n37k,$?PU"sY SI_as ε=H}ϻPrrG5mC)GsTU%I/; Qnk]n] 6.)i2cL9WWz*[Eg_Kp?e鎿o@@ Tgܸi@@ZWė@Wֶ<1gtS\,+@,*eK &pyeX2>ule&뎪e': iiVU.8ϫ@ga ߪ 4w|Xu-p& E8:_FtxFL#$*{Ginj[I)#oNU5WrlIjy;S-@N˄8    @~ìuTn2[YO xQtqF=V@'ܣÖ@;D3&Psś8b5@'ֱf]gXW·SkTLZo|cJt^帆/khpooy9_+<:Eg;*n-*Uhck{d'ejjO[e岫$clqjOLVE=rճl n+6^U $[oINN^=JŹ7c;  U$*^a  @P 0TuugdmW"2m^U@ſ$4<,c;QnniblJ:34V:|9*a\<pJm[&yS-2r[${nYn$8^幭tyhL(xQJz.ZTU JHv6.SxA9rSrP>0}ߔn%H(Q.O%2gN VIġ.OqkBg"oUEYgG^֜K<&;{γ Y, hd- 2G*ش %{*׵NW5֖D엂^^;yg(=3    m-Ei8C8'׫w[8cd 2zT+Uќ(#&[W95h@[2qΆ^9md-*vu/S d܌Pe>{Dw.M$.&S_%'KM8],r w~dXC[r 8\6ȜX#o zu1S|=4@+-QK? ްŒJ.xkrs~Mc΍Iȱ3 fgʦ'Z#*81`gٲD Ƭ%Za] @@KhzI  P@B fOUv+Zk[zo 1[7Qř6}N%.%#F:[=3ю}N@v,439`@\ڭ`6IU]BSP9nU9zBjfvu8sΘYPpMR=EgIe$}TB;؜3gإ]BIJ?=sj&JO$W%l%yH .W    H qd.G8Ϸ-bV,o9[qsתz-u6 TL 6Oer6hJ .upUns +l [MuKg3\8}_pvuVTagqhW\$V9!I3UBu8>W}J%^HJ4n~c ytEc-u V<9ږY.&  @/^p! DHz{19\Ua%)>iU]0gLxmzl5:o}<2*05pDeA 0 f%\Gy" i }'V\.*8c(9!\5Vg%Go+b%FR=ZG%;JSP gtbfZ.@@@@@2 T%nr3Hq A䘩\/ђl="jက>O_Xi h@Yw$PG{?_%EU<90m:}bzh Pr8KٸiL0K=/Q,]O ]"F{y= ~_L|$3#DoIt]HRJq.tzEc_Eݥ"mǎrW]a5dбRte^!h@@R$J 3  @E"Hu9iF.+c ?p4.U}ٽ_;J e\^Q QZv^#8'_O[uG2 +K&ɬ${lk*k_oj-5q@@@@%PV2lMULjI[8NY!wLZw}Th_qơ2bцTx(*IhGm{9dF13e|?{/K((Ga0&jӱ1Rfԑ[s}Ƈb+)8JZH)E>7&oɺe&;E%'1Dsd3dcQyq@@2MM417D@ڄ+tK^Y,J،dž欛[y,^X)mMgK.fHW˺X]kv~W-g@ā3~W$<Եr(VE@@@@w ܏GUݍT{^˾]5gch3)8Ѳb"s:g{7ƒ75Sf@"Je}wrIYg12c)t.}eꊕֵz,  ARF0B@ U W9Ɲ)(kޞ|ۅ(:[mΐ=Z1~1]NbJ"ov%$ZYtUU$Vv>sE/+"    PwZz}W 3;- +Ίz]%+se+eF2k- #˾}[dڔK+$~#6whcez;x5dknBeјDgҽ~_"Nv0^Δ3͡U,b˒U8+l{ qܕ%wHIȃ%2JjlYZoJ8  PA+E7@@v KU97 y$i 09+)% #`fu_kRn.lxC>בkTuI*(Gˋ/crcg'g`4Lbc{qvw?Y}.!X\7@@@@=BO]̊J~91`գPj׫oqH3X kP-Al}*VoY/H pD6OҘ`Ipsec늝zUG/Q ٔ1$t:~Uo7cUᢀ$v }s4;&yR`U͊/#]ˬ$ 5ӭs+qF  Dhu袵  @ TDcVGG<cKj3Y395 z|(V&`H-2Q~#&/-X1*T .QEvß٪jTB7(Gb>=:~m@@@@(u1D&=*Zj%MpAFR3ÕSSdPPi 3%X$ 02n3^(. &_'^{E@d=!Ƕ~U32wu|I2+YbJ"W*#"lW6~pOmlgD@@$⋤W!  % ,YKLOGIIq["sMkYAWvLeEf(,Z.  Cd*:&;WyeFMr- ݺR H{WU)7_B$VHX{Bno>o ⺸rlƝΠsLhɚ!)us^MH+Od5[q\rM@@@@.@RߖfJ>FUM$eh\6m^+Ddz]H25Uh2~b~3p]5glL+b©Co 9Vf+^-qCdβٶ }KI=Hf\ڒGܕy<ҬugN8HOIj\hP v]xW;p)Y7Ȝuzb3Wʺ,}b$K'` St,@h qe5a6Qf-\ 4fH.&9K@H`K=gϑBsBҠbxI0FVq: dd܌שN (KuMWRZc.߉)Uly|\KZKL_!Tìd]lS&V8|L*@W V⻡/|\_UZ;bUtp`,jΦ>)Q V0G_O9O!{#~ٻ6    ep4]11K[np~ĹEU so.gJAvT_Q/G^ 4*Cߵk=ݿk=E{d,<v   $s&MD.71u.4h  5(+)#K" m/;Z(HL7D\Et'[%_֩e,Ccy:DҧȠ1z dNt䍮}%OzZ<ɋ\yGgS{E@"yE֭q 2ξFk?|4Orn= V YuL f GNφ@ K%q,ڔ. f ]@.X4qy'C$ȗ#r֐6:b*v[CdЍ^#ûVIn[S.WHnEq5Va8 =**xu9֞{hS_+ݮg*@@@uƍI"n_n@h|mN+akB*$kh1ӈD-@@M:e-$t8  P'ȺQbEΖqD]yk呤(J" 5F'WkNv@@T70@@(p>Pƫ\ N7J XT"    Ԇ@=1#][%4#  Ty#on@@ pDɕk${WHBbwI7P+ mmBR!    abU!֫HEzK-_ 11 2')ѷpr;gi  TIFD  uI`kdؓCǛ(O٫[)Y@@@@@-\v.:E"Wr#  P$t|Xb&"֒1u.4h     .:&ƘOgW}0     &Pq㦵j.          uB$:X          '@qYs%@@@@@@@@@@Iu1@@@@@@@@@@jO$ڳJ           c`          ԞIĵg͕@@@@@@@@@@$׉"@@@@@@@@@@=kϚ+!          P'H"E           P{$מ5WB@@@@@@@@@@N4h;#G-r{     @prCٽ{wù1@@@@@'II}}{Mqde#^&M=…i޼svmc7t0@4m*-5O?T-cc't"vqo/[onw+8~w\v?cZoVbcc=&8L     z)BYm'eOº8S~&; +/a}.5f|mN3y[$qF@@@@z/PcIl *q    4$5q3OMgfo%>m웕&Znm+~]]=dHMWN[Sv4@@@@@or\v%ӡ>s*>mW'.zJ+\1cA@@@@@$ogg<#۬ի%1 2D6m*ݺ]{[rA/O N$~FboРV߫J℮@<b=Nϱl땛UUW]%|>Uט:zլcF=d={*_}J {u3}>,I;hݢGrv+bk{OZ]Jt-䢯?J:    ԠU&$$Woto2 <\sFxl&ot:яq}yq&&FF>n:Wbu}lރՙ3rw8N?ӟʆbKT L߷c6|0m'#1XS׹W/G=nWo3f7'     p?BSy]V۶ZJFyX ^y{S2~X~+Og<%/2U!DxDD =f/wv@@@@@5.ѣGcbcW۲eKdu@Xu;rH _^]XW{h+) ަM rwokK+a]r^U(uSc3XWukq_1ܬq<|ĽF?lӝoJJ//soTuvNUtbc;S}@@@@@jSBxuGN ow]b53/OJ@K,ӟb}uQ3Xǃ1Z]b֯gۧ*5v    5(PImڴI1n)Q)XViڴ]}Da =nJ"֕#9#+X䵬F`W'U-*,[&-+k^R9b^<ϫfӁmco|WciiFx,OFc{GȈߓgT%ܜysFhޘ?@@@@@KoJc}yiYbUMxE2no9U!?JF*c536E,#6㶺"e/B>cfă5 @@@@.@ھY-Z}wf`U',.^[&fg.ց O?HԩN5o=*aL=z=zTuϪ_dʕVK˷?D__ynrsWuxoʹ3g%c c,ptƪ蜟@^ ?UiK21 tt;F&G#{n=AsUJl j$~;vLZҊ[b:az{J˛@IDATd/fʇ~h,^d\j2f|A}E2y`h}h#.ό$cuڡƃ6%jݩY_f~kGߓq*[ߓX}{R;\~ {Rq/O6)UUM'ܰu}:z)!i۶c?ӧ#xLWЉ۷o3CcX}n]Wk/;wNt⳪n^QbŽoܺukRUh v]wɿv=r@@@@-}Wo>ٱ}b[okۮ̜5KZjXeKi~j.;]1c#K[χ]<[TwE zuG@@@@Jď?ʠ!C{ ~>+|AԉW'?~ڎm+|FBwO^7Ϙ{1>]ZpZ\V;qw _wܼy3i{Kks:Yϵn20n7?~6lpV/Q(g8\8W&s54~?~jBߓPeΆ"GCyGMQPh(O %%%3OG\J\ǎ2WC]''ZqX=@M1ҲZ-ʾ>36|5HHǾ.1ӪHN`~)Q;x׎3W~ƪkWߓjSߓXu {R\~ {R?.P+DyI¿SBǪJ-1up(}5D%6\\S*G#b;t>))rmNbM'FJ tS~xMLL5⤝ ۛ-Hn&JIIIư^H~9Fgs*%k|"    UHJx**-Q⢺MOyMǞu\ߵ~e.ȉăd$    Ԣ@K\N ,:Y4^U6g绢Q$w>mtMҊ<(ܛo&кf͂}J E_SG{ 1˜S{יꪫ;ơ«S]}}}Qc['U~#Ikn3ooe@@@@c>#pS`xf,IP–㾺nn53௾ҸOxZt7Z#M     PWj4u3OM@1="ϟ7\nG_pqz_|cZ%F ty;[[nǏ]{C3}>ܱ"S}{U?vWH۶mfHwh?dlk^v~?g2F'3N5o\3ލm@@@@@N􀱎#*fwu|;ak %ouQb_;-?$~c-m~_<-l_7`SO@@@@5D<=UIS,~{U=KO5~?a=uIߏ4XK]=ƽ11nMӞkbzkNѣk5⸕[Snq;w֘ϝpl]ĵQ1c]aū+>;(?^TrU۶7x!5]w2(E@|~O7Gj!1yqoߓ*1 {҈v=7R}J/o 8qB+u-ZIyrrr*ܗ     @M hU{Svbf#*1W}@@@'б{9漴Vz]cl}O6nV'vi+wOaK5kvӢ͍JVk\"@Zlq         DCIҠ"׈.dpZ_(0z(g{JCJXsՖN^Si!#b         0U_3'ܷ3i*h\k8#}g|8zI&yP7@U$`Sk:8):յS5ѭĴk%q׷: PH"ÏEi>6mZW@@@@@@ͪA׈:iۥ+TI)j lGpZ_됾?o\ԱQܭ6tuswO-^N߭wRZmJBh :Yk:WwD[kc}mԽ9rیm$<ëci     vɘ>~ă+LEG@@@A w3yw)M{#98zz{'2NWkI'8#bܦ}+#yKY{cNJmgQho|,W_:+GF8u?qFbڵ2Ӿ3re+6*UddBD' ȺaD]'wܗd}tp S8`RNp]lT613w\(:W7mOuef}O~ݱNO{޷oNwuڜW'Po}kNR}OzLu7ꦯoOph$|"        A >k\`eu,AW޵CߏJ"əfXoA#i3XU59+4NITrN=NoH,?Kٳ5__{?}>,[W޳%lɷN\+:xIi;ng[:m׉j--Z6Ws7gmF2rJ.JTVz j$F[Pl$ &VI=$κbvk4_:cT<6 z5ȨZw6%UFg]iY^EYn\ݵF^XFr\Ҡdo,cڷW43gCp~fK/gW'Uբ~VwC$4@@@@@@@@W:P'2Ɏ*LJ*ԉ:aTwWUtRn7pO@'[NFHNǛshؚl:QW'+ctW0u_`R ɫC6HX *KG_3:ܮ+Y}Y_WW3klou~KנY{sxoJ`$XTwZΝ`T6+y㳗S'릫( _H{e?V6Mk_{UXW=Qy-}^#s+s~_N=7FBDW#%9_%'iuːrhl_;(xSWow\$bwU_3Wc         Pt.]#~-ϋ]5fyo|tS0b;_e̪mI5p)u۰8*1X;#Me|cu_]׬mY[vύh f;kk*:a['f bG@@@@@@@@8i"J݃uVZ03~`&Vtbp}nID6ﯺy+]%+ҝ> 4 ;@@@@@@@@@* 誠mV-*Ζ[9sү~ r&/ڢςIf꺃W^8{dF&@q#{.        Ԍ@e ]U-1k0Eʽu.ۥqgs}E(k}\ D\ D@@@@@@@@ iZfEc;_e?786(׸nd1ǏR1$7ϭ#        @ vikMfO(6w' ӈvw5ZcΟ=_1G# >xn@@@@@@@@WLȬpDΟ! "f uv{O \sc Ta>=I s{         P;*r5oiG+ҝ> P}kw-մ@>?$!! $d beVEh+g{UAԺEWjVV2DVp!Md9{&d|}%|$o< QR!4?O? @@@@@@@@Z@LJWN9?&-[ "D!-jٳ#e?@@@@@@@@Z@RO߳r":U,l9P-7G*L- $o7#S ""        @c iԎ~CujGe()*EF w) QBw5"        @ Ħ2:Hph;SR_^d|kߖz@DJ(        #m,I "DL6:QHocp91uAc@@@@@S`ҵkWj&/s @@@B‚|LD8 M<|3ad<" @@@@@ O(I$ayѣG$W^}$%&JIiȻ[>*u/=FN>\%c׮]pܫx"n-: "Q   uM2ճ"Z6kd\@+ QL@@@@7O!W]sHt38Cnu u+/qyҿ\ *#GB233=y;)3LLA@@@&s訴 *()dh2&@@@@@ ko륒#Ga<Z{1u4Xz6z2ǝrٽ{zQԂmV k}@@@ԎxFƇ;*$p2Wi3 : PWK^.WuAĞ!    4}̑Gd ^2yrirwѣ_3FRRS~F)z6EEErM7ɌK#7o6Mpӈ@@@P g6`tGA9hU,/7ߎoW@@@@@Z/H h斝-111"9%E:'':{L·ϾYʕ+%Kd4d|>jy&f c/vzYʫ+}@;ʡC9HHLP+f;K]E笁iii2dPQoK gIHH0m$((H~9rDGEIpYɧcv{*Z &ݬdG9Up@@@YҢ9u  ж"n?G@@@@T ))I.dSe{ιDFqx\8y5 =[ws|AYjG; 2rh $ק 1Bf:̱Ln5S>3ߤG$.ү_?/1CV,[&sz 7$ʻe۶2f8 mWҀ[]k~`΃2` [fJVLv_"  4@HXDiQIO4@bx3nNAsΑ%{|݆z&TB@@@@@~n  }xCbz*Sܳ U5YY4 L6Teo^7n(8 d6aÜbk/b MJr[#G8:1}}wq[2vx3? z.**rW^X xNqXiѱ ?ߜ @@@X|Z m9Isͤ@o3X!$ mf,'}b     \surL7X!׭@ݺ[A;w:tNN^nkq>}Lfb^M}26@_Ziy4wы/qceQ74zo'{1eSիVvN/=8G48XKBN2ͱfuѳ9޻g\6}3~kyڵli @@@q:ąr kDlڀ ~'z))iOɂM W&ٍ6 vmc@@@!. L!oU&]uĞߺs^>\бMn?H^v%@Adر]FF̵ck*A5{E?V{+ރuVjvA~dkZ_{pܫ9 M},&L0Nb2W ;SYlɬn3X mkHKK5h|7eI:qQ }FfZj6aV#]f=W*њsrO#Ęzqqq^S'TrR]AaҡE|8'^WB*>3$4,*sT1GRU & ~K׮2bw^+:D@@hIV@kWYĠqU.y\qtݭrqTj9{f7+ 9AtvMWn|e@Y ((Hfκd켚%Ҿ>j*G2׉r=ZHF~y'e_ܥs?T.O/\ #G6qUi+-LV^e2 '[ ~ )_oJX/Gq˂'m۶Ui@@@# ".>\߳-gUgRz\l>M J& "NHHSO;M||b3H>D?kQiÇ֦]ڀrYnmvmGHdHS-';G>X7_"M   @ݳ.?c[[$&LɁ]ْm$t~!}n>wʖwI.$P^E":\MV\Q&Яe5Ehq2'"@6{G\{Ue_Dj@nM]`׽NNNc _JOs?dInn9Y{me4KW]-[~ b-iȑ#;o)6/B%Kŋ۷Zz w ҅ o- k@OV}k[~.}|'$M ?^$OJQaoj)XJ^@wjFy]WMmWU)-.%HRG4@m'5a4\{_ӥ0@&{X5-͉kSI's4Y&oM,"lW_U6_b=BN7HːCeQrW ;5fp[/z>z)ȭ<6N bm`?ڮ`dp@@@oNd Ͳ?\N<-p}%6-ZO"'N-~/;? FaQ#4p7Jna)ȩZwc4'*O@cR$gA{vw4yw{o1)Q%={#}a ~ٽU/$j+eV6q㵂 2ƄEECBBCD%.>A`b`c .h }+W3 w}'KD:}?+AjG@@@FLčf';^&Ѣ pD|Yrg;,2#TW EVS ecK>}emc:t`9<`LzFl>ϲ^[se5cf,\˚yE@@h[sܠfg}e͢/L.'H_WP@}p'T/f!ÁiOzŃ`{&>_#ʯc ^wg_W 0{nUͱݗf@u-'_8Ε3_<4;]\),5^Xu{&!B&U^{ě6/ܣmiֺ ,:V5~HhpyVnkC;rgM't= t]ZtQtvl? \III◼N/44T%ef9Ef[O[jG;݋MNI1,~ITT:9y0it{: :%[f*3.k cǎfO4Ȫ8ZoBN^  68S_lǀdo.!'dƎw!>DN   4@0@K܁mG@ u?X2GĻ+?]H҆h bͦش.]{lS;;qH ݙW݄իl۶k0omjMk     h[2fw+/QRvI&U Aʕ569)_+QJ0Q/=NNys|p!KoEPDŽKjNk]og$EJ^n8$;JDk!vFݜԏ5V@f֢b.4HZS;wv9 {zӇ qvAc]4|'9 }cKV n ݭצޯ$%y?{9{]OҾ:ݷC݃Op_k_mbˡO{=[ڀ$xWwf\9\Ekl׫˫{qׂ}}ܑj?Ł-Yf5I'4yԹAĉV@fբ[O NV m1;!ߏؗk}]jw2ɧ 'ws\w^X.4E(^]9Xi{HΝsjrǵ'6mMf#fo[7idyQWv6#  4'{@Z@'8Ycmƞw V cjt '}ƹ'H\ү_?8_bbb v ml~9rD[Cd'ʎ۽Ӌ<~ ͧC#_   d}UˁTjz$g0@dld-֭jYCi:a8{|8JPf޾[*)]]>rUe,s_nݵV\ D)v_dl/ɽ4sԹ&[Z_p(`Z s fl9 1ɑfp Hրf͒^tFi Ff.k[;^ݯisNa< ʶ33Gc 5V]É&<\OZtZv:tw2kPxH+#Ӡ4hYnSûZsm7/1 %nڮP8,;nٷ'gכ+쿞>b<vN ii%.:M?sO3!f+Roxuҫ'O*14xwsg/Wynw7N-}߾d^nj'V` >|'v}>g@@@J JK@6"hz3/ͮ]rUWE_3V׽ =[wsV&l9zZ+=w?֠^m!' =lT{ ՠ^-9 d3_es4Y{2_nIF#-cnoݲEZ`uygAͷ4ܻڴ~)    #}L'#kM=k};Q,hp+0ok@Dl@[=UN s kvZbsb}aSWvOChWu&SjPor$+*㸿4H6 uր.CRMٞkQLe}oIB+WzV'Z40i$|e']ÚE_ȘO3m^LmeY5K+Рe v/p 9ŦEVMͽ#ŲiF'PXg:w?i#'Y,{th`w}ͱ~zW̾N<\)}zy |*{s,kUPL8O>Bdo~#G̽yk&/P^й>h:fc{Xa5S {Lk\u.7'@TLA@@h:{n @ 4Z͛el@T8Vk᫏tQfsu;_-/>lv!g '2`kۣI=3ݴMbn?^ٷ:u;N-q^RR⬻w߾¢2풋@⇭u&n/v    &6|@!_XkgZ_ځ6GÅђ&)cxj2 7vpleɵ|3~}i_vf⒢R)-)h[`Nu׼Y/drlɕc 7V@k6gt2GDYk#^|jXڇ9YU@3 k >uU.ޮU9-M;֧iP{6 V^"sϛ}UԢ豟)/-Z$.^8e?i:y>իd2zX3[gϖN^%;ǎ8dAy>5dN,o n߾61Egg7ceBNNI4٩wvGp7"   rr2ld1ܑm=Fۿ9׬ϾW^OzQW>N0mΝfS>5(0dР]me\.xMٗ/`+CmE.KL7VΝ0޴1N{Kt    @}b-%VR *h3P \ u/ &v:EN`7sbQzmb׮])))YuNv>&ػW222$NuS矓2&cŅx}nsםN&rqaȼ3:[[lkŝ;ͣFڇ~ԧP\] --J@ Fx]'OE6&G{ÛyNt=M&,<\|7T͌J#MӦ~'6nhX*[[u(`/oij~;Ƀ̐y~4Ƹ5%G#Տ^}ү_?٫\T}wkb-8(( ͛6Ɏkl~Sj-;PX/[$::Z d4HǨ(Y|T9'7=GeHbrTbbcdgIMwvP)"C­¢"X(   M#LP_@(L04@ "ŵl@IDATN~k3T7#"#H(ksU(y=k+V_K1X׬Y-SZڵk'&O}tVtukͯ14rѠIN6YbBVWXƫ\yԜlW7###M#8Y<3$.>^vCz_?ee 'IYғ iK\#i#kKB7<9ףOᩩt)2ɞ={$s5U垏Ү}_)-:*[7l՚C;u2C9+1ׁ    Poz! ` )).cpEs7o*~GYy3p`,uu8ϗ_mZhVߓ Ѵ-0yy.KHHt#<νfE@bo n߾ g.Gedd8Me[oɩܹ   ؏ }zS &4s̺Op M    4@\jGOַPA[gHC>ȕ:8j1Bfբ oF*sqК n47(ȕݷLn<.yJ2\nǍ cǍ3}wGݚN:vŽf>x%XM4N@a 2ްas{ŲerK뮕ѣGͽH}@@@:)"v&:[ۏ5$u5k`E3Xz}̥2+?^SO$''NR@@@@EE{ ߽Gۯ9-**t2oȾ.LfW7mw!渠 _6[)ػ~Z4,,Lͱ%{ n ޝ0*/1CW cǕ˘$"CS>\}+v}:;W;@@@)`gy(;hξK_i=ҊLJU0|C5h冘;} Жtx-~w̹:-yv7p֩-@@@_'SEWg.{8[mKh1A ů*|dՠa-X|UWcͼeU&k"'{M~Vfղ Ns[N61_؋ȔFZW^%'L4-y/8t=Ν+ĬE3 _qUZ>X/$$D}y{=ԭ{w}ư\4   0u Mse%ڕ07r/ΦpeSm鶊c\| u| 60H;q\-ބϥ:    ~?'(),1 Z}ס&;>Ჲ˼ʷ8G7unvl .' Q]I߹SavffhLR)SIcuӦ>:}'wf_Rq҉8,lFkU}$۶mA'7~o޹ݷs>c')&?;Tрa Nv?ps   pl'2ҋD})ʌ@v\֮E @@@@Z@p3GuEios{/_ݛaokxu1zX+h6 ͛6ɎkjV ?'cxh*Ix\A'(:t¢"XUW{T>8G!GKJ%&6Fdȑ#NP)"a:2GG   $`oJZĥv4u6Yg % G@@@@@,.3dַ,}jXrsseGթ`MyͪU>[vu   P?xDxIAIUHD|`Ad      X   F 6-5gV3g&     P@  kO P5]@M c5   > Dĺ[Y)mSsO/r2 "o_[5?;F+@@@@@Y }np;#nYUf08    @uwQZTҺTM`     @M2s剗-=jmsEYKH'md,@@h]1)͚Ò-urD\#ߌӯ       P`Sd%uqnWowHρ   ,VYCfYnA6A5]k=}e}m; WyE@@@@~().#@aLD@@@M h0Lж/c9Xlg1'(@@@@#` wߡ3)fmR 6h@@@u=ԇ1?l\c7.dg, @@@A "D.өaM@mD6gV   vوpŦE[Fr^+knd5y]( tMV"     @IqYWHx_EN[Ƭ@@@Zȱo0GAFUL6,vfI3V'y9.-͝ #    ,Y^\j&]&M#սeL@@@@- FoMk/-*1C‚k=@@@@FpwL{'7#soi4xY\Oڳ!4 NWDWo'A@@@7; @@@@JjcBY @ W@@@ Y, x)L@@@@@@{ S`H? P   дv@FC@@@@@@@ @ @@@+m ="rҢLBS [ IxEI!O*nq @@@W &յ_q@U.UML@@@Z@HXYB!Aĭd p AAArHHx}^CU۳k$)1QJJKD}gUFo=FN>\%c׮]pjl~39%Ef:[t+_|~c@@@@^~qIaIh(J~@@@@YA $$$ͷ̔J@@@I'˚ի垻r3ΐgjyojpes/;yd9׃!CQ+L{N]^LA@@@@I ПZ@@@@@Z'9ąr~)((0Goc'@|07Wрkޣ{q'\v-^Tt<`D[ڪp@@@nx _  L V1]@@@#Q1\ZtLȕ08,L @Hʂ+U#lRs6䒋.Ι(x5@_-~tNⵟ~*'] M&}=1c$%5i3rHջ9/**oIf\:]?ټynF    p ^qA6z 4Ehv0&C"   9zr2KrDMy,=nr ;{kf $,4L4$;,YGNu    Ф17);!T#@q50\F@@@!4XS@hJ\[8 ' ի+ߖ7p H{|zJve& HO>b̺uc|묙g&=z'iu~ytb2Ѓo&eeUޕ.۶m1cǙ`hLoݲEZ`]syv72Sf@@@B*dG{,b_R\ښ[@` SA@@@&p =(67Ԋ #Eo̸bl ܄ݭ[D\$vJ44@=]OeN$::jP{fݸqh !T㧓 s/h57)5o 9|yzqǙnݻq4蹨nh-v}Dx@\ldk;-:V\\s>!   4@^:ĹtYĥv4rӵ36mS b|_Y   0'HNA id1os;V.]ʵ_/{\ڵs=4?C\֭ ݹsrrrr+X{c2jڤ4uvE z]w@^ ֌/-^e?|o w}tY"ʣ5Fvv<ҶZJUV+   9'lOKm# PvZݺw[HTc*7.^%?N˗˼>֛fSHEg!{U-@@@h(<׿iC"nCC? M#}Ƕif@@@@@6$e_Fh8kfGeƥ%++Q Y ȭkl2u?zs;Ω+=]y9vY[uEEER/QVF*Mu[lr]/߿_+CiF#G;-d$߿<: So%E^}Jo۷5' IW~+߾-ba=%M$''Gve) VzYW y"aMž{M ;>PO(%=USy $IRTX蓹gkΚ[8Tz?j_^NQe߾}r`Wv^c #ϣҲ#|zjDܶV    vFhw}W>C ΗM6ʔ-+3U-̪_VjU㵂 2ƚEECBBCDi V0{)Q]д@k=p<ɰ풘(SO7zKN=e^@@@@ڠ@ξfձiPm%{ @@@hhxWü†@ dUdM %ť>$G5S Zll^5`SPP$Kxof.ḵ/VZ)\tܱT2y=6Xrq8^Gw~k@rjrr4Y3W'ȶm̭vɤɓݫq   ^qַٵl8 'j @D    E%fv0q1doL/V@LNrᅓ%%#)**2N1J@e/6Vd KNsfZ-~]2.;s    ~/@&bY    &Dw(s:5 @{̺t*]t ,1Wn ׯ[]nof  qCeMc۹ԺFrq|~qn r3tDGGˠA%+/]Su%l[vu   %}P³_h=9!&L"b%/L@߮!   y?=!c,Zr2WW~, Ԓ[ǫdi!xB@zvctO    $c=MKlZt#@  b-50gO]GqݻW&!@@@B@J\KAN)kgm1FpV "n9 3A@@@@@ڶ@'$$ȩ&{^>ZէMTz%G] OvFӆ@  ]vMiii2`@s\/[*G\駏ȎzNv|q5<(qqqwnq@@@OGVVAS2kW$tq@B ϮO nڮogZ{@@@pWR;U*@"$A9W~?ԖES&ˡCg :v/' =Y~9r\w^z<߿{2t5J Wɨ1cd_;esorz`nkDtWDS@@@@چ@NaI()Q{þhVmVoNҾIqqqR@@@yZ'b?,gZRpZr1 4z-3gəgLAHH%2Xu'&W'䏿Gy=.1eϞ=+:thyjB>hYk "xLf,vJW DA@@{0B@p=Y~G=s]*"  ,;0 eMfm鵳̸F.@VB2׊DhO{6o Ȑ/2WJNNi޽Gp#Gk)**oIf\:]?ټy.7ϺSrJիf9S@@@ڮAlo] V     5 {&.@h {#]v5:p@|8 ;M  Kaa9[g"AYgm^f9@|X;t^ңoz9zZ+=x4p;em!' =An#GӃl}\ZZ*yPt}ɼ%'nIF#,m۶ʘL]钩d-ru:zO0hG}w-3-rm~@@@W 2> E 65,%?yOY     KtH}=c Bnn޼YƏ-W&50✀W Vvnn.o^Y,jd.voqFܫي ~GK?7u:t*5prrKɑ#U_ޣ@|Īƍ/ws{u9ye6f}E=2 ?GLVgWKqqd ?   @ $UdzZN-M "D\HqK{kZ|Bƒ "nC@@@@Z@IaNަ0@L[yx Q+o>} bT쭤4}zWӵ yMގ2W.Ldwu//^,!!T9׺^|A&+Noi` t.2E^iЃsLpOIf\~$=z4{˦OsƏqGY3eڵli @@@~vPm        ڵ -ujn•8ȪWTӣ]YjjS^MV@]LmJJha еoCa;q޽!XnsםXL dmvgu<`$&uv2:ܙ.{qhQrBE`r}Ҧv+Q6*G}Yv>ۘm oF2I;֭[e ZJ&й: pԵ}Эb=ʗ&ԩ$zܹDGWiKGMs1'pLП5ZRSSl?ʏ@@@hn}:]BXѧen r2 =` LAı`U~`<νxZ1X׬YmkN&M=fcZuݚ{MVAEEEr\l_zW,[Z^ j~)_oJXhqGI^    9zUhڟkC@@@@@ښ@Iw.YwpD6-&=2+x7;'>Ƒ:Ⴧ99quhkLZ^{e9 L@~vT"k bo^k_=+pZىǍ cǍ3}w]׎;:sqكR|ۧ?c2DFc哏?+G*QQTH]hr=*_6ɩU_شi̞5S~5ql۶j׮L<ٽ    $#4׿[w ".)hū`M-`?.'pSx    x%؉V" @h1Agnw|n2dP1cI|E6^UoȾ.LF<\+(ȗV@m}_4 hsAނ~Cw'L_4˯ _nBs\Oz-'W똯ujo~#W_s+&s͖{ b֢m=` 3yλӆ[t]fv^TLA@@@@@@@@@hMv VraLٷ3gWkcJffhLRd|)rImCsp }ND$kIOO7c{e9V4zĉNfᠠ ɪ>m۶98X|ܾ}{w~+=9ӷ$JJjSMе'sAQk    @bRL㜌 Si      4hF\MV@VWfϚ)˗-3Z~˗.gZ/-O=d׵hm[eӦ9 γrYld!/YR*_2"a֦syQk[^(,(0&Xbsur: v{Z=^ۺe,Y8@@@ڰ@HXY}IQIV`      庞!6*\G6&PR\jVυ6ַ6Y&^×29 3ΐ#G$$$D~o1c@+6T6o$;vleH ?'cxM\^<8O%$QzS]u"vPWĂ k6)k\}*VJB $dI»Le ɔ}; L@@@@ht>@LhF@@@ *] F{fAgtG @|$:~>bz衲okt    @ qq1BH Yqk&it*  X*P8x&fV " $ȕ}:+O@ 3V?pIJN֫2R[[cbb+ܜqԈ锷,&ۨ j(c5cÆ 2gm~1/?_Ϙ)jŋD9   H%f^$+DQ{HX2kD ʊJ^_mZ5'@,SQQw$**!   쥮l| _rLF<ҷ_?]~ټyl6I6Α Ο"FA     ѡ:q   @G tLX# WyVVVsʔsϕ&N_}Mv 2_?X}L: 肩rϝwHmmn7fXTlB5J_:dڅL^'*1     d"@@@@@{nEs@IDATtS9IYRSSu|隗n޴I4@%K[7rTd|=:K@YU'ԎwCڵK uuuވ#,ğ|G*gM, ruH{X|% کޯRF# 2x`Qm9!gsTs1YW2g޻H!d p4Ypz]6*o(*5k~'f%0r5W7 VkAWn))!Vݯ@@@#Pm(0MN"D;(@Y߅^'^ Q DGڂY/     `}Tfi[]YU3 :Ԏt@ͷUtݳ TF%%eNn^T-[foEe)uevQVk/bЛ뚷 6_5 b{21{rҸTгq}[ժC?"Gy@\ed;Vԡ}5   V'v`FCpV] 2 8n9 F@@@@G`ԨQҵkW=,{w˪ {pYY.o`>Ī6EEl^;me;ԁ*XeD~e >*$Bo|G ]ibRj٦#~wi؄D׺ $Ul>_\,|}f Kԡ?5CXuԿQP:I%OzC    A) 5@ 8=[d>SȾI:?554m߸Ts/de:^h0JMM^%G)1і@@@)PUT_X оd"n__zG@@@@2 \Qe{@@zza-7٫ Uq:C{r^U0nk'n(*Ygvkg{p}>IKO72x4Uk]zG*ؾ}gR~K&M>[g$8p<6I]wߕWOk ׭]ڪ~@Q1*lx%Bɖ2ٰaCtIj%Vw vЋojEKVβϹWz{8V %6TzMCmt_g3Qѕ _k@N,]sdžV ~DT:vW_yǢktYTtGGeΨ_F]VAZQxaɐ!Cpx|63+[ҍ`bcbϙ#>p[\ /9crN@@@/еklI$K .p07Ffyd D#  Q W}`hm$ @ ?\T]tٳCc20;#M (H`x`,+dݴo)"*p7**J걤#0⫂kU [۷_?ݗ\}oSK.)+׭UҌ ·uWS%fp^G(ӽH䂼]+ƹ' *PzަwVF⊽R   ЮN+;q\BlC Zf&[RhMنAay[Y     <=x Xe`"zr%x :aWF/"%%U)/(Я&Qꨩ7t:p)2Kթ 0@}b$$$+WJ}ޚ?Tp:Thu8Q9?&s\yV-gXV<׺~檂u$|2/K/w% nuѽGyt,9@@@/PP[azgDZ3uJAA{"hb|%n6KE@@h3Drfxml(6QZ۶vgC2a"PVWwrz [2(ЫW/ettL0Cu89 .Ewv̝7_ */wcbbu ;RRR_?m&̛UY'gie-6x,Z{̘9S.68; x*5kZem~N:Y~ڸQ^|y 9/._s(>ΞݻX9A@@@@L2F@@@ff AL@ #+`),ܷ+٭2uyt 5d'g^yŪoOYx6뢩Xۯ~YCF*SKg]S'*w:WoACvPgSV5U_|!Nج*[J~'=*tQXZ%9'     d"[@@@@@8y*+.H>}ʡzskQقcb`dݺͶs3ڽ忿k3PX7ot4hiixBQkj22~12|uH-C+)پAj+裏Ĥ$I2~*鸹qt @@@|(Ե3 |hEU@` 8 sB@@@@N+q\[m$Y ZJԏkԏ(Oyy,Yħ>tO.mue˖.@@@:Fk,=pҎF"OSt-`'g&Pb!6_    mݫ ӛ%@@@@@H0SlG=o: "n@@@5EllP@@@@@@\#޳r@@@WV@:Z &6MSblݷ F   @ң("D3$ A v%#   w,c!D\Q^)y̕M) k~A22lq>I|NB|jKe@@@@@-E6   4#P㨕؄OkVh]2)Ck;4=m{;v"!}7vq~86@mmL6GiS;!  @ֽ5Ul}-C@6OmkwjH3'hԤ/@@@ V< Jd #4&Zk`36ա69@@@@BWYO {79!`&e *8V,A p@@@@@@@@@@"    @ɆR=Y0]    Bӳ2 \O 3U@F7%         wgS_O^&mIdE. [^I>!%A!|:   @o:Ҏ"     @( B(ݵY+AP@@@F,@@@@@@ b!z;oxL @@@O0@Y@@@@@@% "lciְL>Su^pGjjj%66F>3y7mp谣Kl}nG~TU<&&F~3jĸ;[N֭[3k1x"-5N@@@Kp{4T&h<֫Q@@@@@!tAħqL|vk߲yGqbbyEvb0{UWJmmY)SHu:Z\,k֬iBOhQw~e89'   )cCdw-?MdU *්J    @H {>5$%g&U:BjL+ପ'czC mh^Mj7Hs,G||̝@\]]-?4ܧo_y|M=wCttLv_) Ee,naf*9r2nKLLM6v衲y&YP *S;KuU֛zu`O$*V#>V2@@@@@@@@@E{%k Ҥtsә[  03Dr}V @A-tAĦViiiĪ&YQQ]֯X-&9 *y "V맛lټIH||م襤dya˒ڥ^})`O?#=,M Р݅Ӧ eփ4(W&qT%]yݚ5r5W y   ~06A6j}g0 @@@@@pV:BoPSϑ#oe.}ZwϞV)QQQW{m~Z]ީS'4h: w)6/G=M|$$$0GcJn#? UU|9U[/cOgWWW[jƌ;j0^jnUFb3 7fr   @NkC8.!h;8>ѵnnwA(oj^(̙9"     @h U n=C`}e /ꬽ7y᥹ru׉ u?qDGG`oY~:R:5j}9~DW8ӣNe׽./?͜!N/'%' >Be\0$7fV`adС'HJJ>_r12ȤO= B A*Xe53|_P F/hM8ko툎:+S[eqtvุ89npy駬jld կkUr*ٲyu,zX7S@I:Xت`,?7GU'Dk:IeW_-Ӧ6|q_^{ڄ>PDȺ߬mOF#V]MOL<2瘇*޽wkoĶo "?)UVI >pY孊GYb ]v;i]dÁ    :'O [48]SRVV*?n ˖-E ߈zt 4‹UCZoTw߾M(5k:]ti0Eͧ$'%7ٗyakqqb*֑0ڼ~WUU(Rt@e"nVjk]fef}t_B,YE߾}u6j_-'ג(7\k*:mՑf#Ge"auT಺ղp̔% ~~3z ={M 'f~     @ *{~l(*jv\7zŕWInN8kjt䖻=[nj(mqF*gVkYazovE7xG!   M T'5U;K|Z5Km,t"]h, bT[*\ZZ*zXmM޽xntU|bG?eqΛ2E3~wzCZo~ouXO#fSb 9VVf QYgϒ!]tI&@@@@@ \Í2jO7ʥ.n#GMgXfEx"fQyQ8p`2':Hp˶m\"3m[@@@"G #ߕE*9+QknemHZ:kE dʶ2 `2Aw~:~w\㻻LW۷ol޴Iֽuo?qe̪={-?~㥮Nz_t52?Oe*[g#66VOظX.xy}v*XEvN@@@hF|ks㵚Q @sjɧ ;Ļ[6o͢t@U\ͣ?a=MKVJs  졺ip   X"tx& l\7^k6 m}p>{MAD|%/ws?^Gөcrȹ=OV-xM?T uDmH#c:&z\?LVm=]?>Mw)_~E^9{zn4`,٫ngn{f9ȕW]m]Ⱦ#k<\mRzmW.~~5kreeHΑ(%;KD (@@@@N,=^ɆO8P8C LUJrF0]% O?_sTU;˼/=3 Ϝ~\M#˄jk̪.Y>и_4S]]-+ c9 *?OcK\ .Hrxcc3   & (uxmXVWMvzC!D,6[{LKmu;d+Lo*\k2 eگ}~͎=bHk/?WIub#úo/]Ҡ_~)ƌ#كoUm9O>cJ1أ^W2gmwߑC'i^mdUPTd$Aƞ4J@ګVkA`Z}? ԠjsͷHI~U   @`v4XqFC 4fqAġqwADj* #KFF$/+Y.۶mkN#26F }Tg_{MS6chz7fe½,o~Fuض]>Svx c3) -laڨ,Gׯ-ee->&   @@{o;+]@4̿+*wwƗ@~Z{tUU{6a滳D L9z?nbUgK{~fhp;x[[SbTb/bЛ뚷 6A]S?* bN3oo53!O!G*tZkۿ̝7_N9~GDG866+33Kf~#   pe\_b,L;خh ЁADmSzS1ooFS\eV?=$JW-72vS[m)8S@@@|H12Ce@M{ATFoGYY"o`> J(*Z몀޶=,÷qU*#+Y]87U 2V.B&z$:l fXN'~U{K +Z:u$]ȴK/gzXΕ{[6o[gΔ-7*y)ʮ];[LС   >8\4|hEU@` `b>     pmiԨh]a?𥍸8bFGg5Forr<ȣ:ӯ;Ȝgi\UVOSA'DkmI b_]wn-^P*2xRjw 's>kd`U달bZ~U|0}жeB#6x݅Rxkpw'](@ff(##C/ösBbbԗwԮMy|K]voHhI QPP bk֖ow]/jLkCm!   @ 83KDY0 @ wU掚W#:ii-?Nn7i<66V{%+EчjMUU|`i|<7Yq:]ʹy~@/|q3VF$TURm#G_>}x@@@@ \{:ٮ`fT꺖6yzV޿|dC.ĶzA,   e{%̒mkw 2#Pm8Ǝ!"*HV6w u_z=W=ƞ4ΪHfɮ%R^9!}$-=hֺzjo]%>;h\]]-[2i:#Oz2~Z[nV"],̏OIWa+$lom.,gJɆ ˑo@Geeu$[CI %'4t$ٺuPtE/xX LezEwaՏmc3CS*M]#q5? jCh}ϔ!   or/"P>w|bdwq#Vd? ֹDEG{{Ψ_@&(oZv 2ٚxINIm3%&v?h*h Vﯛ=?g]k}˯.T ={vWfcJ*hzqE|I$>C ޻wG@e;{]ٙF@@@[]mj+HGw@D ms2y\D`\,AxW   @ hSu: .7Ӡ"/@ evAawsYP @+qRR'oXyck\M ".Tʀ'fKBB~rJCCyx,Q1~D%S6sʫVe kު皗Vs}#11QL—:+*`b //~]rV{IYlʒ@@@ 8+]Iq:l˨3ŭ^XMHZFvm^@@@007CѦ6X[oU@,;oMΛ/F^E_111 ?e)))l۶M^7O^x=kd93ŪYm|=㥲h2fI-3fΔ/#; <>y+9o5ʴf͚&t`yN:Y~ڸQ^|y 9/._s(>ΞݻX9A@@/n tf(ݭaBD#ֳp@@@|03#!@'GO@ gu:jkked?**( -{}ee!V_)xuu)yڥ|rث7?!swJP^)*@/K]lV^%?C?^q@@@ 4~r:#@RL Hs@@@"ErYg{؄I%IE!lizYfqPZt{ˀwRUyf/x5Q?*[pLL [vUqcF585xm r . 29-M/Z(jMMQFfُ?&_!ɰeȗ_}%%۷7hV]]m}ђ$IOá377Nx   03YClǁ imv@_%JufYoD7#   @ $( I2`ʔ59= )tUDz޻6K(˒%K|3..NgJdV[lYR@@@)+ZWR[3pY@@@@ 5ht_+VeShYtDܲ5@@@@@ GUDw!  @efAF6U@@@@@U5-W 3$ A.@q     l\b\/ @11^ob\lkf)'@@@P(ߺ'˶u2 ZKb|bp̊YtkW#g    ژ#4ٴrk!k~A22lq>AI|NB|jKe@@@:BgL@/N@@@ J%Mrzfɶ;#cѬO0}BGV|s&أ@@@:N 93I^QI02@ֽ5Ur{qVh/C'3NJ   ,gաi;A5Wk @{ lt;Yй=o@@@@ bRl b{=b X8]YݚM=z8[u"*@@@,Op     L# @ c@@@ Uj+]k,dS8ln* X@c4j\A@@@h(`4, Wf2ĸ_ +@ b#l,)@@@? (>Ma?Q|v@@@@0i%@ ;J8 /@q#f         a$c+7g`F|,O n@@@h,PRӸ<_;N=}swaln |˷s{ }0E@@@.woj.StAΠ   @Y}@" Ǭ;N̛+JWǭ@@@@Y][a\V@;IpH>!%A!xӘ2   @|R&4&,M>Lq:곇'Ƶ@@@@Wh~y># z j"Ap   !(`>z-)gtco1wC⸳p   \n{/_) m'u'؅KA~Ĥ+@@@Sp877GÚX    hF@IDAT fdf!}ףE ?I.f򉜞/>#8 sB@@Z%K[ b`G'ےBlL(*a+*zѩY_;3:    f$Q)W`yywu*ʝb   Bq*Jj|R\\YD5`    "`~Qx,c]/{LBXQgO ~:A!~>   03x@3^ <".$G@@@D/_oddžRQfmKC庽̮Jd1leŻl{]6S'6t|b;fXFE@@@P03)wyw}w:5+)`Sl    ֋ c_Y$g$\dMz]rXS?& >Dz#  Mn3soLiZT{eX  p@|"%F @@@@A03(kF+l}fwcW`~ܾqno1%FFsq gEq9   ū'U~L||K9F|Lu~@@@h3(6Tg%۫?g/u]h*'dyOo  AL@@@ T ڋ2{njyFeS<f*;"Iޯz{@@@X3(LԬ$ ui TgN 9ON}`A%@qP&   @h8N=Q2K4}TV 3{[p%Z%rzy ά@@@@ pΪ=Ck$3  30IIkPr{e7 87<&&F&M>[N; Q   enzzcfW1}ot_&lYCCrDn1c@@@@(ߺG w33::h f3)IX>-PC2N 鼂fZ*+ܜqԈ锷,VÎ@[ݖ   %Oiݕ7f٘M3  ls$3K98gɬ-`z4]Jfr.^]e+Lٱ+#߯3P ?@8LINJɳO?%{> 1a@@@$`~q>Oj ٛƵPH$wl[3 ;\A;!3rHi .-YL:uJ/RWWwg   !'6SQuoݟS 67{< 3+KfANxv#J?tlC p2S%**tOw~A9/@@@@ʤZn왧wMOVn=p3-`+s%w%((<"/`Aj|3x9^}~G"<:;Ļ[6oQGU\QpFe@@@PAjSm| FFYͬPyfPb~p?aA P/SN; .پ]JJJt%?^u5a#^!   @-OB!9 d%";AčixmlYC;2(7; `8A7QMܒ~+?Ig!]0Uխƌ+M@1   -ekS/B۸8ݲ )|~㗇{s ^n1),Oн߿_^z9y-z ]m$K   a"Wt8dE`@Aܬ:HȡNkj3wl[ӧ\B֨2r .>P1;N!ᾎ{]R?WU M+{B $K=ﺺ:y=7ߔAܑǟ _~GO?#={}G & p,N&.Y>Р`Ze%ݻW^7Xb@@@hvJ^9ɼ2FvE&PwJ{x7B)')ݼ; CZ\$ x?YO@ԃk5MNW_qr7󨬬%%<2DHII TqmmTV/͵ՅnݺĤ$uziL^Mu~z/5GI(F 6nUv{Ko}r󌙒d wU>SgMn\Nٲyvar]wKD+*ĕA?0g.W'Lc=Vԣ|9>՗mҿ-i@ { ^C {:^`%.!N;~YGtll dڐ4Nfn=)..Ka횫G۾m{ 'VOkQ[SkUS|˺_!O=UT adСSԡOpQ5U?Jxz ٵ_ݷ} `M#gM]/U(#/7_-wqdٲeruɣO<O8Dy~γVf bɎre3_ʏ?(.@k,z>}Ț5k$#͕Ye~W62*@@@g { 7:v~O7INt#1ђݺ`+CAUq]] 1rLן&d__'?gغHBZ'':&U{lqeIm_^U-n/ҹ )lޯĖ%Z#`D qn(ҌS 7n.wgBF|'㧹~ؠLXnOFv:#qjj_])cAɿ1BgA?}1iesݛ|飫}nC@@@(-+|N*XͷR0Hܹ9 =T-@bԘ1:8f | ޺u`"U ŗ+Fŋ=:P+ nU@@@<vo@j]ׂjksFmY#c^Sv e߻w^l >VׯAZ|x; "L:lYb̛R}Mn&P    @TxYii Hh!{vx$P퓴tqo} rw 7,^v|3O=%cR/ZE@@@@ \yCo:ں:Ttt4)9n-]*q#6#ݺuQfP|9g',55˰G-w(C@@@@"J@%~PGlm8ޓS}d#:2%&v?| /~'[JT[uƝ,LR9G@@@@D܀b;#TAll05䏽Ov뮫k@@@@@ b/T+))OI~A~}7 ,Vz| ^cYhdPǘ'Ɍ?YTbu|x-X.Ͽ4WK9X^7_Z%.yoϛ"n5FбZ'     _}Fܧo_u2OJ^^ٱc,_'#W^u3e<5_>2nx+ ʀ|-EIr|e'( iD=Į`г`bÂ<19bl~5cݟ@Ai]@콝z|?RUUiͶӉ~A_v@>cQkrJyI qxE@@@@=Z`Ŋr?IEE報T;akY] a\_ڱe;xebm N*>3'y'2$*׷a@@@@voz"=#$ۡmVk͋`>4+_6` 4;Dc#Mj#߭Z%+Wlg蚛+]JkOo%?\*~򱽘W@@@@HqX({rO.'tf>oy{{/;vO%%W]%ڵ:]ڴm+]0'aCc7xs++Νe͚5yE@@@@H qcv#xT/>2Z^1" ǀ     PSJa2ڴi,Z(-vM,>q]Gb @@@@t&{:G@@@@@@@@@@`O xO{ǩ/          /@q 5o攮iw F@@@@@RJiQXpTll     ؕy~G))^6Tle˖@@@@@ f|W{+[ "    @ҥڙt[uvn[N=:\     hi< Wǎy[1@@@@@ ub7n:բ$           f8@ ׳Gj]~ټY^}uO!~ O.+bz޺U_DNm-(9H/wYG09Z #ax\Y#q6K{f͛Kuu|| *a!=WZj% 6ʔwVTlKThv}9X>3n*{vWG0HIh|?~gh֓$ 5PX  $u޿i'\_ [SIXX $Xu.bieZ[Eh'AY'dN:YdΝhJ)M;mMڵmghy]曨;%IinW;:aaR}Ub;xҥ*IrZS(3jIMN>|8@4D/m6h6l O>˃s~g[KC&=>!FI0IR Uڇ][nM N;͚\]R"C^J aF|,]Cy[­2v{h|?ԋk߫41Jye_x $h|e_%r矓πfI $Ta~GbUi'$̈@ۉפI5[-Z ć8 DNn}r)t2lvJyma;nc{e{ud͚5h'$̈@~<:qUKMt5}aA=b7nڶMֱ$`i敏iY% S4NmXwj A/kIz߹ҭ߲ۡ*yAԿ]:tp_tљ\|iٲ^?˿_{ڧa?ތ5sw= WT71wv|D>WZKg/@˨NbziӦUL9;/ua$}gMssϴ]{۸q>vYةá`$Nnnz-zG%##Ze!̙m6gڇCHR}P: NDŲX DN."+eU:Ez>rI-sC-[,5Æw~k]+[(z󷿕u&0MĖ5^>~zN2ɧXsmz=gl{D=H6Y]vb@`O&S(_~sra9êo7K9!'$*wM(UZ 䠃s&oܸ:'vm79Z֮)%@IB֜_~UM*ӆa> X B;swbjO^zvP0ghɸN8!6nh]s ڛN~{|/j0a$+/EWOݚâ5w0=Gk@;%x@M1bnrە>h7D qR2E`7Q-xM9ϓ`=~{yl]X5^6d]VXa$7`]eL.g/>ZE#>u9t$ӯ,|mk&c.kPOoArc.iގW"Hv|.u;%6 ݷ>fI'Y vhHvU= )G8B eڇO }x\{Ѐ?Oi>LQ vO'Ѥaqֹ\rхN/нK,&ZOχ⋬Y"C::vb'>zS^17ciK#R)C+M<6PԘ *eeer2o1=woM5-5S di_NU\E{kTu}dU_ < ډqHv; V5n}=1z`*̋@*=xbŋ-cjYxWgEjA;'}̜7"NB+傁ZD_*u NϪ*W]>XO>sks[=N5@*E_"xR"&#b*VL='C5.u_-_uA{oz݇λ#+MRO>DyylP!]_*/_u'(wsLTu$VlQ~O[}Wzgcys;Ζ/ԺXcnvzdZ49X>p3@>//EhIݿZ]vqY ڃҿfͲ}D^CR}_{< 4fNI~{"}1ThʫX@ׄH}ۏO %M;d,u^+{9:XYy"I~ge4e|<ɓ~2qQ>XWR8LI ڇ'M27g˗/2r:wDߋR=X(lO&zv5O:H[euh-\(6mS~$|޲:^ {bH@*o!oS^3ǹb][dζz'4Λ;ZD;xM@*zs}V/y}Pm["G 3$ î&y]re֤&M'z3@a:^)><[ HX'Ѵ= P!xku=Л&176@;Wb:^nfxժނem UgG(#)Px"[oLLQ Cre,/{l2=##liayXDϡZ ~NzW=dwȶԲýv)`q|\כDg:2ӌX $}c{P-G 3 1y#?d. ^iGp&V c͚5U+%H va9!55.:̴je˯Rw5ӧ>=Yi'LH@"ڇV/{A1O7ҟv~gYNhF{.bO>9rQX 6O>OGFrWڇө",U B9hLxci_c PaW`?\ǗZPP 999V/bx붭[o1]~Rka`lW*\_PP(XGg*J op/ʇ˖l# ){~~gofgdeeʦd%2}?|N`"m'O9E.b[@/>N >9X]6VsvYW8$I #ڪ>c;@ BcÕ2SUe_}匇|N#={wZ QG_`g%K굏W^~ɩ"ġ`$n'=uI'><&&[ Q snp#y nZ7w<{xk0LXO~N;*vt;0'*DɪUZ9'Ӳu[d $}D[7Grlo-wK1kꨕ{g;aOm wuA|H>uÏ 5\s{uQ 4ab-1hr%TCb_{M"F,PW;@/"mtM5IDyoYd^Wn/6I 9x 1hDhEV-@qn'0bңG^5G ez;LѣBx|diyI0#ʰ'ر=|f"@2GGxA{}xk0(DM"yǝV`@/厑W1+P CKrufzX'Mb>C~ghb&`n V7][ v`?\6h<>1C<*gc3=N5O@2G4u}D6 @jD/i]Oz>s ͂Nkoxc:Np0`HI+g[r:?Svx>#5@̫x}#Ј6L~g@;[q={oR4KOB?6y4?4!Y2q+f͚[ T YcoHIדVZɑG%{h~ɣ.,=>l ^%ѣ%;;۪+/$?S@Uk# `*̋@ۇ˝AԱS'yرhWZ&=.á`$|!iܺ:W/9CDW[Fs᤮ߍXkT<6E(i<ֻ~?yoj\}t1,Zd='~ݘ4@ Yoy_ٺZ:s>Zn)O'L,C§f&(vLQ';M^s# "h'J^Nk5CI',#u;0>r֥8$X v<#&gdUcM|NNՑʟ]'տZg/v6nd|k[D$}DS7G4jlO˾w+ soIOw@ygN)_~g= bzykNl ^)MhѨ  cKZ'5x/^s/BnU~4@ dŧW_yźRMؗh'Ht;ۥsVU5N|#^LZli-d}G/?vtNѶs#͵CvkMOB1;!i'}8ZYyy_`}pn$❵rI4 ޫ:h'Htnh.ugƻF ߁oTn;F[8lɽ/k׿ɓnn~iBL`W]qy zCGZ4 y}Yz^qH»'YvLyHt;S޽O_|ɉkCtˉ'HN"_U֪}rѿ>Tb-M; Vi<#auu"Q"w;8ÃB{tع}mvqHF:h.$[7zN w 3ghP&x W]4X.J|ѓc=6_/kΚ_ZZr?2 >˚eI|xR}]o}0O>V}b}C DNBKcKח[ja.}kv`X{Ӂv-x>#5A+Px]X~?'r O]{;^h@Io߾=`1$ Hv_sS=_Dr;y^;b`0@4$*Sy]'=0Pzq{>>T Y#Ga:UH"Nwr~Q:lݺU'Y|yز@<}?dꔧn =<⪫z/~s&,\hU78wŕW9?kA dPe8g?eN:?O[j%^6{WOD=cy[*XaN|@G4U}D6hp ˯p.Z$ rP2y㨣d8'_쨱jW3^~Ydas[{qk=+X/п` r1eG>3E^,ēr=YI|m[ź Ww8 $}SS&ڎ{])C6&m:h׬Y8bO>7Gi-rm&+>̹1E Ԙ+dǘw$,1ɏ/]j}W[.9Vo"5 DN4i\s/e| ky&hª {D7⎝:IC= kG(;wי7YN # &n!Yiob%O.vB>f{&>wY?qHv;Ѹ>$+sM/֪gQλw}銊 뺉N"ON>pYؚVWW[7DO@IDAT$⯿YvP0dƩRZ1I~|f~#}Ͻeeb`=ߟ*M4n[uDl~4k̊3o36uqrטNO>8 Bzh&$'Bc4JvkрWM]{$/z ?,Lr^m9tڧ4 C9ę?.{gЌp r֬G=/ۺf$fMsJ1aH>~5e[sܮI@%x@ۇ}~MH3+=>5@  'S~J6 @%x@4DˤOenz:"MG'y~_4c=V񦵝NwXkC;%x@*hFV@ DI5ZhT./5~GHvզPиqbkB)1?n'}tl=s"|E|yN|8@IUs~v/tNq4zslQ"ډqhq233Eo:|/3g̰`':_oZ||^cϺCKth'nO Gy]Iòd f$ظyWH]ۺ HmڸUh?FXk׬r՞l9uTt%_[4pf;Þ|b P_l]ƫ}|ᇲuVޟsm_?3f߽::B`"nViǎwo ! CoG5cbf)>5@4D{y(5OGϰ=ؿuZ6J 8l ekh]:o-!]7ث[& kyH<ڇkLMr}J @j DIk]mbP\]U^.\^'떪_|8Y /0+8]U=W-ӷ.#5b7&V@@@@H1XU$p:,C@@@@RG q&S-J           8@@@@@@@@@@ 8           @"H"N2@@@@@@@@@@@ H"N7           1@@@@@@@@@@H!S͠(          $B$D(s @@@@@@@@@@RH$z3(            8@@@@@@@@@@ 8           @"H"N2@@@@@@@@@@@ H"N7           1@@@@@@@@@@H!S͠(          $B$D(s @@@@@@@@@@RH$z3(            8@@@@@@@@@@ 8           @"H"N2@@@@@@@@@@@ H"N7           1@@@@@@@@@@H!S͠(          $B$D(s @@@@@@@@@@RH$z3(           hp]Lxn)y~arQ1OK^$kK*7JѬd܌bNA8X)vhO@@@@@  %Y\$5xIAИ01㤽@@@@ $s[A8Wfw6cq<4sZ.Odaf̸PrL&Q_2v ^@@@@@=NWh%mkĄ77r#    4f2E 8eCnϛcxֹ+U%E %RQV,s-awXB~"mu={E@@@@=W q]2_ ^alA#s"f\k    4~z"N0O]7T3Mrj4BkKfM<;Gr W<\w Y%);gn3{fa\ z     _\7o`rI )[NůΑ+Č#Qb@@@@.@qJ}I GIz7wbFd=3%^WzUU"}|rfJ~^     `xu =,V8u,&콃`ˉ{ 1    $ܛS(N5ezps*Oj`uf/.ClPS)?/c Y^Pqfg~n#7#]\E2W;5_-麎Ih*+KL;FY2hD o-aW],GeS`gJ9=% ̱,/eR\(ì/ҽhL_yv\(79Mw5&UaCX:Gf].ᒗ^"& _fgۤby_<3/7\W"6Zf+0yʐ|ni*aٶDl̷SKA~ψYrdê"4<2Bɨo>gEou0e(寎a/    QˆK=$[vfVU*OCFK< vrT:t;ڿ'n*&;X5Gm.U_͕;ZqؾČ7UȆY2o 牷d3yCFJQ1j3TB^2#•     ߥKyZH{ٝg ~vЬEsSg?oƷ)o? _T\Y8bʫҧ{砫6tO0i2a`^ueEȓ \RpaWhSB%o/ ۅ\(/-XE/L9"ɷ<6*aS鳚K骒-fNU/uGP UUj9%Jɐ*W+S5U)UUfYscLn*%r$+9WשE~3åK#ese{&/3/&ץ6*^IoGE*ۘ!mRڬ畄M{\k)oE1lk`c+d6~HkFK/{3)+[o޷\}w\̰W*^Czu1a;FK"N7[ #.>M OnMWHo~'ӧ: Cש%9`?Yԧr g@<3 ELS ̣ݺU/ 8)YlsB0^˖ϔ׍l'IΔc.+b=p>"Ih2Q< &z7̲K3OiL;ź^4\PcM7G%+fOkʹ(C{UlM8"gG;A@@@@ Gzp> wJBV/Ҹ; I+Wp<&sDyb|ty3 'vHZ5GN48ps"s>LϻC](bޱ}ӽ<,N)o vT~8ѯuMcO{ŞxK7qܷLW;v&} (csG͔ʆwfʘs-LekzYq 3Z{7׼w.=ӃrPc@@@@Mdڴ{MZFx&b2<:dT1'Xg7OJ\U~/7{%j/Ş%c{\qwP,wN]hu9/uVFc\:'XwXpYsUUQMgn~|i߶d4kwY"vrqDuԵ.X!ꯃ bh|0p+yeE M9Pլ 1O V ]q's4!k~0Yls.bߞ    ɂ|lW-xSƖ`"ئ:ک:b٫2)Xo=L^;_"?-Hr fz3B/}NΗvu% l0c@@@@WeO>; H,"slP<0F 5%KČ혬8dl?lE"\k=W=v3Η^/'23vb^f@@@@ 1${7wMe=Fzf螁ԧ,|ҭn#<˄?Wz z0FQy&Ԙ8DaB0 %&e{@@@@R@`J'oW5ETy?+L8ۉUJH4x`z(1H 39\=~d;d+.+lsIfv*    "%縫?-Dż~d+mLk'i:zuH ;͌{2~FwɗA#?#T]qbDzYkd K >w3     ]`O^f7~{t7C,6^xl4uwYdRө@gkSe 2d`u*dR.flޫ駚{\9 e4cLs'=Z1`Pr~~Dr c@@@@h<DLmW8л3hrS&xJvqK(>aIo;GpY;&2Vgu5Zy-7zuU%huvv.^[*o>J{%7=%So&o{WYxqQWeiʷѫq3COKW+v3.fI&ۃsg%y/שh?C9"    ЈuxdhL}c8];^]2);YUEm5&NxK*7:Թ^xȋ, {*SVj߿1鷝+\s}>\( k F@@@@(.>Djݣ><3g['Mc@%Heut*gt?d"sv%}/MoǺMR||m =JE ȁCa "s]GHfC-g_}Tv3}Ϟf H&=;?n6_˔SҲY(|뙦lj@@@@ JVڳHyyy{l;Z+nlJ@]1݌t\["Ll41 &/Y_*ƌ cUJ& W`lQŌwOzssT;r@@@@Hb7&8Ru֋X 3@@@@@voP1;wƴ$ǔ!    X_^0\n1     y7h1     Z[n%%ҦMIKKJy23S@@@@@ bƱ>0Iıe     @M^ْ޼\.PQ!皛y%la% @@@@@vdnjI" >DT@@@@R_I&n4k˶6Enժ8ӹS̬,k۶9A@@@@}kܙyd     @lڴm${hBZn-{=y 6Y     @Hq$FK@@@@@T^% 3qSO˛4m*itM8     D3M(@@@@@ TWW̯59To"[~5`V.g@@@@@ܹ3$ǔ!    4Ybz'1X׭e֬YcO     B8m.],O?yTVN1զ͚IFFhOF4ݶ[ZefZq%ر-UUq=;G@@@@TXČ.qf{A@@@@@xK}-[Z6n$6m{KӦMevZ믢5ݩrykQ<     @XƌkjjbZ&1;C@@@@p}rrbeЄڷv{e>Ś@C%sg}viZ_jJDq@@@@@+1cz"n+J    )&$ghP֙ӤbAjJg[H_ތ2@@@@@ b317*     G`ΝA-jkkC&6 3f"    4 PT7(8͍     @ XyF4 wl.[~U6o/f@@@@@@njmfM =3>5&     Z|@@@@@@@@@@=:t`:jt~]o4          =@)@@@@@@@@@@HI @          Iĩ>P @@@@@@@@@@&@q¨9          !@qj@@@@@@@@@@ 4`ziiiJ           XԬB@@@@@@@@@@pN"3!          @ D%D@@@@@@@@@@ $ǔ!          M,emmm          4z"nD@@@@@@@@@@ fVq$@@@@@@@@@@*@OI          $^I"7sD@@@@@@@@@@!$'@@@@@@@@@@/DLoĉ8"          I"N9          $^ ؿ7ė#"          @v$v          )!4^S=          T hqL@@@@@@@@{<?p2 "A%V@ ?-6m0JKK6+-.BKY KRZH Qd =s33 $s.y{ϓ   @DLu           &*M4y           DF7\;@@@@@@@@@@@hZD`*_          p ԨEje!          pQ D|Q^.@@@@@@@@@@hM؂          E-`%7/C@@@@@@@@@@Si&s8f8ٌ          DDH4@@@@@@@@@@@kx<#  p~3NzWB}@IDATBJTeǥdw3CkHnsZH'7CHLaLbEHYx$ْK򊿧_jC@2$`3F>u.%cb {&ZdLކ*@@@@hrSoIR?LO3BA M,m|2:|sg^GUH_R::" 4fc  p$kzd7*˧ᐜ&Hr_d}?I6@@@@.2Y3u#}_?&wlY[/1نt<k<rn`n w#y^n0:  `H"n! @*3 %%FxsN;B-3?RA hu&sN>VAk "-{T E,/g }|iҧ[yg Zpf7!IRh!dPYhUSȊ9rA"    \d 2y|⌯Uti2yJI}jc۹sFWG/]Ř \ e꽏DP,Sl;ˊ%g#jĉ#efbtiBE%H`L/chdM wT,D=͂G I2 c\1`q O#G~'Iܙ[|^۫Ϊ1Je[;Mb '^ e@GTag)y1*fNչZ Ȯ%'/YRG+}ĚcHU sesyL% 5Cgf8*ף5Ng-'I궩+Ra>d}d\Cr앪S{KJ_|m=_wPJ Ί#Y-kSz1sBl7h<┝"qd5|,(oĸߠI7.C3bġڷ\O].Cr%VU!9m*y3*ū="juސ:6b!ó̶9؟Qҿ>+ڠbɑX:8XZ,㞩=eHnIjThjnr Tqn9qL-RTlx]KݷW-"̤f=+y^]ՠͿOxqZHG?&Işd/jW0P"_#O.sG҃{5tن򿋄o@@R͸q*a{"1 ė5# p T­#XQ=[AR[HΜ *wp$U&E ˫v(ɝU6Tkc5L_om*ȷhb>?Bⷪ"'| =UI'j]?AT ~ۧJvv-<@ Z)gERd`=[BשYmXn=˼m7RIIcKEThSd4$GvJjeM*OʰuM"tnO /?KU&VtR) 5MRDqmCǴI2s%$\2/g zԼ~P!$u㛳dݣǷ?75⿦_ֵ,RPQM?C%ٻ_%W -    ԭH V[W@'P o^+~Pxeиq2{&HUw/dqQ+TLvI3Fl4+od/Jʀ2o-6t%STiXuqk!1b߽2shIfa=Ulӟ:XpBݾA2O࣎TCѧ~-s.uH.Ȗ.PoRd.>25lzFbNU5Oo$I7ǎsqtYK][}rs B)Zt9+e|6E{l3!g@Updx31|W5*v=KyR?һJf݊џM  hZtF@.aB[w3չ˱q:"^{Ϩ>OUսgeHбR)9zt^߼TUxzb }#dт\VӪ2Wm!<*IvYi[~72T8*XU,(٧*4Ʒ*;">Ⱦɵ IQ ֮.K>V~ʷv$CZYdSQEs0П^ǀ|oj,m(_y@0\CEkdn mDl&{ )FTt1*LWM|&:ڗ+}    \ H1U:_BlIqdz1ɚFd]TQV6:[W\g4?,A;pCf5o٫ms=ҽ~30H?7{ܼ.J4LYrwYLH$QW\WʗyYr|>uyuz~"kd!.WUU~*ν͈sZUmps: y<#R<{R.5_Ͷ^GcUՈ]Tx̠Ǚc4TS٪Wv\`kE *8L Vg9O{fMU]JM|&c&fxU    %`.s-.o.-cCUֺnc5&KD{|1$D8C v9"rHkwTHyUm6T?(mSqs}UX,^e&F:o8rM*ٳ(*ܳA;={7g1u#p~$cيScBST`loҥ]J%6#N^RНkLY@&r%+!Fe@kL  p x.w*rpUGO;>lEGѾA=k|::lP(NeiL 'oGJui'@{֐^cP|D|:OI88lU]n!) 6{&UWhN@uX*/N:Qz8*]JO xbTgCiQ_cݫ2[    @ZxbP>ǺK?606!I\PlJ2 w[{䁔H^{A^Q_cǚ(Θ9viZkEQ!C㳧@U ʮWw%rF̱$S#4k!Vf7^7MR7C^]ز{@爕_4gd FCiU$=sSTBct!j6[wI)6_d͐!V*zi'W~Bz  Dl`@Pss ]kΫNF)g5W̛`RGM qNlJdAgBjÑjWH̙2P%-udP= uQ5M ӿܷ$IRRT۫XVr%|0F@@@@@N9O*d]5m)dJQryfalOkkqc"%8+UTjș_Afll8GCv 5~%]zc 5YY~d*) F2:*.q/ԾeU\s:ٶ\t|ZVQu{mRd͛$w<(yȼIbK6{  PC+I&5@@[`?\/F%ؤg8o:"^tVKH;R[#Tj^-0op/t=qJ;BvZx}')M,ݠsPDNYRWtpAw}/3_E|%W#Vt _)pR@@@@)Pj3!V%Wt޽C[zjVcu^Z6kS+e߅(%E*j͒ers{2(ϱBfIdG@+k?%˲e dj2+i5{LrU[xAWxג*)^?xyySdmjy NY8qR *P8JLv`#ƊRr8 ]5RC$LeF۩}:e/ |c%9CuTuǀEX7=-sVeume077 ׬> (U/̗ "YWJfOg<5v>nGʘ˝㹟~L|6GIFql)K!cazfŜ@ky  PunEO@殗dI%WvVچ+Q2'r.Ki뙸3    T oQ:BFB]4dY"3z@a-ܳthdYt0N<:):ǎ*w5fc@4I-?nE%d͓,d:Rf.f Yp[&SF wbL_"֯r"   \Z%fI#u;"yn:ޥ]K%S*XJ WKwdY5}L`w-W9&Y총UDXm?}ɼ8R)x*jʾޒ;R: /C-a}R#qh KAyy9Ft G6fi# ]țk,xV2D42jJYbu+}D~9zۍ$ɚ9 ٹֵW{ d%2󚦮g,1/N:\YE@8/k8B@.r%gqm&̆K{rO_xZT^Rt` U*$ɔdJù܎,n_IJPƫ nћ{$t z ղ&IѪ9̚%3{ʵ6%ɐːPfY8<>Yhz[iPUgd8̝>YRJ3+*Q<T&EogYU[( d{aސTU.hRL yd/d۰ex[,,7yI{Wjfq]XOʂ}"J@@j$q`@@(Y-ksRY;cDÃ*H͙NzD|UNf^I7[fڃ*h9U#`X>P,yX6'žym*iY_uWծ#dެHuŽEN[8u+z=r{GWCǎ8OD/8^GDn{P^. /z/[@@@@hDHIR:_șB=5GvrqTk}| Vt)ศ+۱EA`WWl5cc92aGmLf,QUMZ*,,l5gP-ҎcUu A#0GʼUTugxFq>ޔ2ʵ^ $s +QF u6g81$Ekdx(Y{ rޠ\aojN-{G1*[[BǕ)^ϧVjqcRy0v @@@@"Ou%sWJi_x*^{?Trml2y4Xrъ2|[|.V\ 1Zn`ƝxmSӁϜ1+^q8rILY&43n[ۋfDNURP8o#(ŶjrVw|9zX1X}Q"5!Ϫ|Qoƫm/ɎJNNhi"3z\ mèWl]#bUx^ՌY3Σ/zu<81+  5hs M /Zmjsi! [镬^IP瓮WWj.VI1*VbK͐ħ@{&Uga#@Y2o]`Jmݶl̜F8w*^f-~/CZ~F'mٴZ%::ʔEU{׻l].g!ԥ''>(ɪrodm V3?TuRHoPSu. Le%mc5HWD%9ɩ%v#{-S0  P#>}pbbdߢ;@TT 4ҥtwTq%ɐ[MY8'G eV[*NjIUlz䀺.Wz%F4Zu/nsȔAr/C6n%+{ TJȕVM%yTH^V*ݯT§^5RTqLG?Z3=T{ I;~U.ۧC9ymU4._DTܷt_cy %I%$6ϙ~Uke@@b͸1I! vI*/4+8C`X;kV B@@q 4of  %N*x߀l7=M Vom%MirmHo qX&,Kg{WVnrȀ'Lb     и.h G$ytٳ ٴ}ۈ?En9ClA೥R̎uS-  u$@q2,  f#Ce!3gKڹCVm)ɃizfHZJv @@@@hNltKeȣb_ V?ߦ.E@@hz\)  ȓcɶi I֕2>3QU!vƃtkIծS +G}U>     @Tb3dB -UBNc  @hs449^vէ>  |GJְ 鞔 ڷf*i!8_m6H]k‰+xu꠯N `#^}av*   @kD<'U )  T"pF$3IzE#rX6["Tޝ e#N >"&OuI  @@mƍI"e     0j3\D\@@@@@#Pq 粘           ԇI9@@@@@@@@@@h@$7T@@@@@@@@@@Cs           ЀH"n@7           P$ׇ2@@@@@@@@@@@ D܀nSA@@@@@@@@@@>H"e΁          @            @}D\ʜ@@@@@@@@@@$@qL@@@@@@@@@@h^'+_[oM<(79~@@@@@ V|ٱcG7E@@@@*$o9Dy\ &Mcd5{ 6frYi޼uVyy yiҴhL}7 qҥp/<'y={-j[n5 [XnHBBIQrl@@@@@KAwޢ .TGy/`%C9}挜>}Z^yO׾1N;KO>Dle@@@@@%9]Zh4xɝ#cVd;!8)fRr;_; RZZoCȨ}WhZȐ3#ح$by0N!    @#五 /]''}n6}H%ֿ&ϾZqJ\!    @Ӻ_r[ ĺ#Gѣ{>5Z G-^b%B!ٿ48FgsɁĉFvW\!sΓ+gUڙgX2|ĽFY8*YX͉    \pe͝/=ݑ@|>4EtN~4jڊq,jl6     @T".XLn(/.[fy VO./z}qkX>t#YRVV&:?>'۷2e /ź}'vfV[~c{u~Sv0=յ_iGC@@@@!P&U]DumoVܵUǍw˽bfrq]njd@@@@huDe*{-Vf 7:?~H t"M1~*zO?m%|>/r#Yjkڴd%3~5=!#M#98䓏UbO֮Ym]Ds 0 B%Zh­={sbk[Z]JOU     P:VJB޽QxةG' zk&]v3ƈ~[ߒgf2u8Wbrz˞~Cٺ~+笯J_^.SW6D@@@@ *P'Ib%Wz1sJnrW{U>Xtubp|FǏKk>Wcn~51:@Jh;2U18--.;:Yщx+}HM'ꦯI'2Ϩ:7rS߾ݏ|fj̧wS f$LtvɣH 'yn_31e|JyD@@@@j,P^T?PvUxQ,_.#Goulb >1/R}Ldg%YQOjհ'go웅x@@@@p)wmm;y|eBccUw[l)}T.3N7-!J9yℱ/@@@@@>JKKtWu0ݷvD>|Z^CRQ.c KOuq@ϟ. JcaҪL<     ԡ@% ,$񪫌KF`Uiڴ]uDaUٞzYIĺRqwo1f}mkִTՉwy}V/u5}}q 6TF}<OfӁͯnz^V5I˓X>7vs0v7 U c[ayZj_     @ m#|[ߒ ʼ T59sɘG~5Nb qG5SqW{˘9A],b/a,~-gτ : :kƃuvK/E/Gpa\Ѫ\\W^ ?z'&ϱS@.]N3=~\b7=/'AߏKFs%8Ѹot9~h Kmk-uxe>ni;Yjt/`     PuZX?s:N}mұcGD)S:j6ʹ@+mOhV%(?Ϭx :Ѷͬ/=@IDAT\˷i$kY!nS'\RxX\QD[(JT͛w+Sk׮z,:u2g=֪U/˨H^ϚFuk8^s8^=lMkS.6zBI]2"b\G] UƼX~\,whgϞ=x̩$v 3U1D;~ :FZQk޼QާcC^{Zp1:|Ruƃw8s)q7f]|Oכ5N'1{Rޜq =iYJ:0n˦M짟,{d{Ung6ΣcSr׈Ǔa;    4 D:(O6v]{mϨ.: ~JEEF?7]lclدP[ƭʆ7ѣGiĝw1l}㍪ aiOe]&ڵ36ɾ>: ݽ{}M)j};#O>&[vm҉e2     טA3։WըuP̷b XU_m)޳ǘK;F_WQ27ċM<@@@@.@$J_ܠAmy3gN8!8`,w2t\Y^/_qwǛtݴ+NNbMW6My<pBmokw'̸P'@@@@@/˯Lvm2g`,_nm{ @/=H}b<8n[aU1dI#_u}ct=is> {d~UuۻwqX7l"  <3XE];dg׮Rwߕlj2@@@@k&W$`]-By &؛yh,yuS0OVc.b_~yqT5ݓ%Gѕ|+UI<"b@@@ n_Gk`w?n+-vosOSn63f  SÑO]EWkMm -UtM'k:gϞrE{ 6U"{Uʚ&ߢ^ewZN'[J>W˖?yGyS~'5Uڴi#e_|![S3U#3gl322UZTt.@k5ν罆^0N@*{R=.]+Q=.]oWZM7{ݺgbb1`.tnjݻ\:p+Ue}ƌuI;o{{3mGa\ޮă'- _G| ` =1%D ߓ6ix:yX'29c}foʤu]}:W'ڸWrE-_n"[F2GEWk8x,ױGUn*C'>H&8rҨ`힎*]գury[=q?Y_HڌI%bGvNW%>Ǫ QXFWޯ]^Si-Z0z;_*     @5j3޹s 36Kt`U:xpu  \:4)|j]'{ҮҦ}+3:D$`1nj]t4*:qwEXV)_F']$_@@@@@@@@@F*?[j t5aB%3vJJ$^Jnfi29i Uz^ou%vt?:Uߟ[CwA:y2P&GKgɣ z6UN&o^' _UiՌ;]WEJBkuxnck#-2[{+a]'bdZm* F_M'MƏlO'ѱϰȪNP6О>#Y}PlTmş)5ByNoqY #i[u*v}/sO׹]'q>aS߳;?wU~tbu@p Di@-oOsٴi43     KYf6WxpDt@@@.Z]AT'DC*6iUt*eVb5\SdIfNR-|F{(|Tbt⯙zY'ۛ\dXc6i޶yЮO{JR.?{[>0uak[ղy,mT_3y_/S N_F:'FbNMߩGT_=k|EUj}{한==Q>lYcڙ6P_nnޯ:9@l޳g:}F2m3f# {eK9 ʩSL     J>}!\-.:#   Z@W;rcGY5cQᴲvZW5گ?OqF%+:k4 ך@WUVe[|.zK9 |c5k޶};_%wUWuIVU ul]ٝ:9wb&Κ㙕u'̶OUnyY3#X'jݟk{D{d5Nd{|:}G2Y:9e`l_{vIuqnƼ8H"nq׀g@@@@@gϞ)S웪LN?=t2涫澔EӪ m4J'         T.rE{x}E, UV: 4YYquf~=K8a:VL@?HE؇@\Ұ@@@++JٻOVɄȢl/"nVAvqZ]+jUP ֪[","Eق(@ 9{$d0~9X<\!#C@@@@O]6utBy@Ԏʁ#`r2ΤIBwFcRސEеq)7d1Mf    DFA&wq/2eW՝.konWlBk=;D[OzM~`Q@@@@@j-諹։H2;9P -W;35qf{jx-v⓾(A2!@@@&0izk;ӠϿ;ίPR~zOm'6!#    "vVPHCn9pbZ+Ö`}/^@`} whnA]'i ^N f1`@@@@ `Ѡ+o-m۷U鞹q-ULBseoIv6,XY?ٻL:IMɷ~e9uq@ns=.,&<ٶr ٪DEt}|@Zt_K /\VFj}k<+]^N1DDTڲDbU2jx<9}cj}>N*F@@@m| K>}g:;[h]E_5e 20yh mLU$2f\@@@8Y05gɗBvvIRb * SrykLW1 S{Z3 oYu=w"hm}.4`4+V3vU}*Ɨ3WĚ5|3j0u Zq5f>뵤uߕ4qFh^'{Zr/zWkssO ޷h} ʎYp1{ᑡ2 ĕo4[+n|9/+4^_clWXMA@@@85֗}-v% ږz!|Z4xNr )L{IE\+p"璙   43֪E3 W T4X6|b}K9}@j+ &];;8Rw)o-kwsٵv>8^4p~nJRZJJdӁgh]DTl+ER_jX5cI+ZM&Xv^9b+c~i@y ĕ84XW/}]zwlZjoz}M{=j] D^5WizN_z]ͨn[{u^0c磴̴ ofslRzZ:/[pG:oG>q@ qYi   QtkUZr+ ߴ)U^+-)M]~p D\7E@@#t2ٹq*X7Vo$&! P/V`fKK$!:IGkgՀV`jDTh g r皭hvi:`l7vwI냎(j7,f}qkpsM.t2 뢥B|g%V q-%ͣh\\ᤵ:+Z. Tݲ;{Xz[Emf,ຯE`\;xXfVxķo%gt=΍{%?۽2@61 Dc="   P.YAkliT;?'_= uQ 7v0jB'ِuqOZ5t'[eˊ{񼖷c}BA]w0v[ $s>sH_;E_Sn-Ln4w |־6|tٲ59JVAy=oڇ3Xƞ4m}hܴ9 `e 0_;jGفpi2L֮`Z|YW2ٟ_ۜ9k# k4ta}ЅUo3ۯ}u2?\:xA4'CHdúu_63V@@@&3 zYK&@ Yah7g1#  @fv)xE36f#>L/3%rzM =E+p5"ʚle zWWq@f_eGu[֏#[MNE#5eFW˜sS"cƎvGwޖ2wqˆͷ&풓TJJJ)kVVe{r*V۷osX^uRRSei_d1AAq@@@Ar넚v6#up>K9kK ӜFEqsz+   lc"q.L߭sb`'X~[C56=7tiMÍ=. mڷ6\n&ٻ/$qiR+p   @C kSPs DDWMT~!CxChw=}f   @$#]qǛu \T{;HVA+W}^l1')7&X{().cǎUA*wehvǝ_p?.;w@   @ x.[Mџ@rW'È_>A~<(;5~$14~rҧo_S?;;[$$b΋Tib2 ?mҥK%k' .\^5˜ࢋLvc@e!2e43cǎ)ɚիeK.] >N^zUɓeɢESOV8VVy&9j +i;ns~bƓҧ_ ='YYMv_<"  z{8gS?FpJ"i)),9% TL    Y S6v6QvujLf/`g"?5M3TחsU—@S <#!xOIemQMخ|ded+09>>jPݙfE r=h@} @ڹǭAvqqq G{UO;4ߩsg5z=:)ϛWg}Ny@\de;vZZm$?/ @@@"ܹ gPI\Xϣڕ' J&]Ap@@@nnD u "XGO]o*l@vұc'sO,U_|!ޗ;ur+hmu999q+Xa2jddl3uvV~jfD~s]>!!KLL:/:#lGK_U׿+PJg k_W6IKqw V>xPZ?[-G@@@Z{Fj (gr Z }\rSooY_rrS$&Mjjv^͒klBZX?5Ҫ ZS|߿С/x2g^3bթ\^;GJJMtUF,ZX,_(c}ݲeKf).P}?'&#q޽eE-\@ޜ?i[[ԧm)w=h}$ORyPwO)Þ={KUoK:HaAl߾@mc9⒎C~xM-ݯ7րZ~!PIM(]io 5|N?R+ݗ^@-Xrrw@@@NN ;( "&.:uL ;|ve: P~pw^xGs._~+ԐP 빤d)+*OmUȡ!^xZ:vX{sddřm$ &,L]]д@{tܹԓ3$33&v$yss @@@p:<*lka}JwdoC C   /`g.?*T1R%E5u pBBڵu8QQQY3 ݷXB0IIM5,;V=*[=AY;g$V˱cͣ/?.YYmd|2^j\-I5{CLfYUGZMjk(dvZK-ӧL2ଳ̵Zʴ&M   4h 6ޕ@=}C]@@@j{9wϑv'2=,m{{@UoęЬ»3wjL2 Hjpj޶V V.{0ϊʧ_l\sղmVSAG~%.rwcY;wR NKIrudڔ͛ͩprxjl#  4@N{ 8!͝-/G [L!   "PRXR+],ĵRlK~E`b Nuޅ2 e1&GRYj [%::Z2D^dSG;ꫯ2KtL\sD9̈́ܿ'뭠[_K~^zםwț}Kbccg^rͷTݟwWCspNw4hzѲh΍5ZXZ4Pȑ꿔َx1yOGV@@@()pEFG4R ! ALL FIG   (WDBM]e0y :t}/Rբ    ; X@A@@@Omb|"ΠQ仓N&Z{ j AM ޡ\sDG7ո   ? Za9cL     '!rs׷MJ۳ͼ9't41* g$9G\Rw}GvU'NNu&e&fr*aa}v;5|2==]k;v\/Z(eeeU?!ҲUKs<';G>f;f<)֜wʿ>YR-@@@@DvfaN35TW  м¬pC5n    @(-b$2:BYnOf#>!՝x$'#]+l|F ">7lg+OcԘ1e}U>%2OJRRRsg <[.6\VAnݻBʰ֛o{V8W#e8۴i#{8 zbNkDtWDS@@@Ko&dg`1HL/_ˢpCC_gRpg93GMLR;4222$E ;zTԖ   'yH:e rȈNZRHSDpqhLė_qtj?.٢Yyq ~iS!22R^nPX#iiim/ʯ n|ޣ9ee9v\+4gLͭNiIi^tQA.f,9V DA@@@KYVf ``LzI̚\@@@@@mV mh@ e쨑~2A~rՕ2/9ϲ2Zv7w9ę2+~|jt&LȰaD=w-$_z_{V6}*ݺufNݧ9@@@@vrD@@@@[.wKVcv Z 1O&m6[Şwޑ.]ΐ # .;n+s=LSU;5Yޅ^$o9w)8$$,~yY՗崏J 4nժ>|9ˆ>]&H\ҫW/ջ߿9sORR 39_[Tn՘c%:*Z4rց,YNS @@@z ؙe՘F 4r6U@@@@@Yl,ܚPQu_NsΕYيtQvFFdeemWƶmىMb{ةr!Yl].     ? DDӰ @꼼&SGBCCرcχ E3w "LJF6ӧs5 }j+#prŕW]ϗH{zZw˸ѣұcL; rAKv,˗-s=6j@IDAT ڶ7hkNtjw)O(-G iS/VE ?{:B@@@&=GiWA5\ i@rXMAWXWZӗ|z    #Y~'Bw P\^I 1NÁ4-P=ȣ 㲥˜ejn•=CCNmiS@zzz함@ O<Ӯ'ur dO)n4wp_TttI(TwӢu.SqTS]RW !=C}>j ܧc[Vyit.A9hP@@@@p 5%  "p"*A{OXh瞭@]V9Y~@qU8ݺ?VϯtV` L^b.W41r\t5JQQ \G Di.Y˗9A111ҲeKf)eСfJnݪ   J5ߛ'Uz@.6'FW_3    4$9A%#̷}6M:d-۸4Ź 4Y&_r~d@4`v=s z>-ccȡsrr>jE3ky/ ffu.{fۊu?V;77Wr).."(+sxxXӢ7nr\۷ORʳGiг ?1{-fh-\@ޜ?k?ܺeKmU887"]21*o&%T%Irl;J$ _w$tܵT qSeu@@@@@<"I\Zn9L]y`#α洑=K/{Ϋ9́"lAq.'($osˍ ?5 '_vljpX5}?fW2+`s?4{`:N:lY7}iئ=h:^o.w)qqH 4+{+vq-gO̝+O=9d{$''˵&ɻ{<"     0xٵJk)@@@* e-)p؊=@f@`;p͵9@X0D{aϊUF3 g%K.R{;VZ[=iЯر3Wnk/\GBģnj]v>uZZ2v|!ILLYYU#ᤶmdvZ,Y=WTZʴ&M4f@@@[Dt YY4L}%E~;O@]t+O;]bicqz]v7zBKJKD>fj#Fyy.C3ϝGuԄSN3kK,/[5!    $v6& |F "~d'XgOO֬YU53sY]UuaYKQa|W}k5dAmahS5xٜ׿/7H^Ll? V{# ,QQQfzrΝHƶm5IKIqm[N֭d;~[n.W//̚ @@@|h_~۹ٵu[Fڀ %Jkm擗â^@=M0#wO/7 &${WntWX\}ʧdСrϔ&/YXһw  (Æ[oɧ$ zw:Z@@@@!׌s24Dw[ -ki'*~uQb'v4;v$YF-m@a-,uyډM]v-cy OU_4Mu|֠doA]CwǎisZW.=qO >rh'#G@@@ց$aOq = 0/I~₂ٿ^t !C{Ыi3Pn޵iqg63bNkYנKsJ|:9Xͯ|Q @@@@$"k%|i$i (A']'W oҥ',[g1N=E3>C2 %?t-fY|~9_Ժ}q^*Z4[ffղ -ߝ\CV&rm;n3vlx9繡yEhfD?YČ/22Rk#9-:wi7:P)     @S,Z\f֭m5W_-]2N~-5K-ٱ~Wv2sr#;#FԴ4 6Lun5=w-$_z_פLA@@@!"BB“>@N׿ =1¾?\+{ee ;et)"bJ8]ݻWޜ7O&^w 2j++:OxiSm͒%##܆4ƘqaaaNe~*7ov+o;&EvWѳYs5yÇk6@@@@@Fxy+9e\>}{ϞrZ˴KJjOI1z]j%H?tsw:]YL .IH1_]os_rI+иUV]JuHJN(+f;:;͝Zl%EE6ERR 6k.[KDHYuUaر-u Kt͹.wӫ)@@@|p6r2OQ @&h C>O@4.Ӧ'wvfXh<ԓvUoΗBt x6ZaMrmysھE _.\><Z _#}Ya#XX[b\h-~@}^J.l`Oկo^"   y9-֒}G@$d[a\[ :YG]|yo;iB}6ܱSg/[ZZSQ]4!$ק 2DLfƨkSu5W(]t|$駝nuu殻eUybM2rhkډiF+w뜟ׯB}mwϽIV~y`tӯ   N Sϕ@4JC_Bu93O/ϕEbk |} ՟#GYVm_ kx+5'rJ-ϮM'`s9勱~|NA@@@\ <82&@\p+zfȁ,I2&k'o%dРb-3w{]SMx "ֻĚaob m=n KέG?hskkӧNif;u,EחKJJ9i͗ $~LgUddk2 Zm$?/>#   )vpyh9>8F "n{uJo%77WZK0 +-ʕ+}KE@@@ $Xy.qmLngW+U@V-1;gTU]h;t.ܺ+pW-6ԩy,1l۶lWcrlܭG'66L] OѠ~jfD~s<ҲVIOm޵k%X< {5   tZ`61MwQ,\ׄ\:[_D(ᣍ "2V!   WBM~N*) XTyd(@ONL£D_wż8:g?2} }g uVwsR}V3x+!t ջ=L'yG9Lu= 2O5zX | N]GW}ݴ‹9s M@Vܶ- }CNQ|2}жvt+6 ݅x w6;цHLL4&$$ؖXRZ ;>%FQJKKONsدgW2_ucܯKxp-ۻߏm}[/l#m!o@@@@hVv;uS >'󐹔vMu]/Z:wMd&oJK~IukWlީ臘1t}~:緕V#{۳ks.bOt.Q GN6ZT?~3h]fÆZx @@Wo & @Ν;?OoW\.֭35dOjˎ3!V_,;n CCCLʿ~e5V/,)qqmb$=qkM-={4^;WzrdZvINNk'Mww9ڇyD@@@B]HxOS&SUeWQ͝R`}    3O?%eeefjaWͣl{ TsHo[9zdkv<~=hh[-ǎ8Se}5;ejժFzξg%|!s耕qrHkImz6q@a 2^vs|ɢErݵ]w.±k)x@ڵke@@@wmc:Q{V֦n# ?zD@   Ufwi;8%l6 \irRөsS%]һ3wOc,\{a<{z ۽Gs^3?+̶/-[*\}lۺTome~Gmr\@\ॹ/˫o̓? A?#Gf}lk W_}iZ/AR]G4XKii;f[׿c=K5uW^2RP{x բm}iLZ^r )YMOnMS2 :'k5Mb2kO,1‘^yT6'@ pi2flƘ@@@@^Ԍ12&hl=:HOظ8_`ЫA|Kv.EE`>a;?-8,, ƍ֭[jlyR1P~ vEX#ZJ y/j@@O}ʆ DZ~M~~rsseҥu3""VXv+W.@@@N@BZks#n\~E? P     HN!Jw/N<4 + Ӯ+~(A{O}M@@@@hc*ȮXLP-_NXuT@?{?jN$"   -fDew@@@@J<n~`6sF? Ϟ   < v1jF -VKDvch(KB   @}Ww=h"{U2g@@@h`;ç񳁻;@h7mmf!-ZHѣRTTTTF@@@@&ْ% [TsA&@q=L@@@@@qdʽ֫Y3W;!    h    @dtUZXCm     @hO-./y`   3mbv<0Wzk3CWoAqSF"fA&=F    9M~;4W3Ǹ@@@R +՝}/ɠ@Isu&(A@@@hRW{-8wϑZξv{9B@@@@y s4=M8Au2 2D:t ֭/ZF@@@@~;^Z#,Pϝ/(rA}o_H{$s# DHt   1`/dFD0O*i2M]>]`&$B)}Xs/x]ʪIXX|m.9YJJKD>fj yKHhDX}l߾]cmcnDF웮@@@@HHs^hv! c'X3 ߿O5W]UA*͕ݻv:!!!& ;Fwf>@|qٹsz9s%zo;[[=#  @B{7'3xgu}h>{=6Ač-L   )}KIU+ŝrvwj+r` 7+sQ#嚫.'__v2WvW~ ~Dy̴1r9zÆ nݻBeuK/כ11&8i   ,Y5+)p߭'2c ?ҘC   +ݝp`4Si;H1E4ߜ/}D4سǤS.riIVe˖keNIM)).+Ӧ:͖.]*駟.\;_v¬Y]$g?Kos_rI+иuÇ;병,Q&۱DǬ2`@iղ.))ɴ6xpoZ"ZD>[Ue!2e43F)ɚիeK.VP >N^zUɓeɢESOV8VVy&9j +i;ns~bƓҧ_ ='YYMv_<"  4@IQPdLyLH|4 @I;w]xw`@%Pa2     N [$u]lv=h| +&k'&W5_mkb\rANf<&Xz۵s[b'WȀSNXDm61Ǐ[lܭGXfT&#ckle~GL kF7sZx{u7z1\{&Y < A%TzN]liw1ԓ3D$m+oltjw)O(m5ʌʕhDzw^S_     @    pr wfȘ5A&"#8Ȟy@dd2s5wqz: u(wi _nR+766Vyn|̝G@W^k /~kرjLc/чweɢERTP(SO7."yyIn@m[޵i}ɔ3O.^{%j }ϴ$I߄NLL4WKHH [611ƪTxPZ?[-Gj8Cډrя.@@@@@5{nr++컕KdV oMk:]kNŋgPsG3bթ\^;GJJMtUU,ZX,_(c}`ԲeKf).Px_OHܻwo5Ez ,7ڏnkU5 H+G>L;i@L XN=KAo3+mKIX_b`LPk/IrrrL\S~V莡;dou~(=_+fT 46Pimݯ7ՙ,HRJ~ThMDצM? {9#S߹m.xADS@@@@psVE g3*P<KgssYzk ;fi҈P맶r̪츻 ugfγNswQ PcŒBϑ9M[ĞǭyT4mkf={ҥK啹sモkdv$+`'+W:G!  &`^Xp׊0d )eg!pI?}$~}S[n1U , igֽଷz۹sYh65 y/-)p‹.mgܥ?68 [c   @\wqA`̞  pr@ܧOIvvL:I'6_ohly^YVCxeǷѣGv,fك9z7:_=߿Tנ{LVZU\+]>t9| jHkImVnf333ͣ]֩d"k;nU_|ᬩŵi< ڵs겁 ٻ㇒@Ti WAwV Y]]uk/uEWذʪ AZB $w='IL&޻;$s̹   PyIͳ]s*,X3u93 \ɸۥ~}?:: d5W˅kkmEÆ٧H>}D2pwqʰp9h9Z.kB2ۺ't9wv{ɞpYv.A:^|Q.Ykh& -@coNe:a{E'X=|@ⲔN:a˙g%_p7nL4Q~eu+m?sM}Y~/A@@@2 ːU,dwRs&,AYϧ~*zퟃ@{cG=Y@4Q~G\>gV&qnѫ׌t;,YLYVWK\~{yvfsr +6ВSY cNrÍz 4cޱÕؽw0}4*Pَ<3+;Eف   P iB8"!=,P7oȓc-7lq\:|[ײ5"--M2eK&Djj4OI1uue֦^kV&Y~gӷԵ}v*g^}[x9&jv>g2ۙ/ۅēOIOwz/g) ǚ5E'u.:z*Z=csB.yw}eS׿uk@@@ x YVV]+UE ¢lUnz%;`Y} 9qii%ʄܬyx#UN=4>jw@@@JHhk?۵:{%6nɵ2|vdGu)NFd!l\N0YtsE^fiv9dMߡLfR1jլeWh`N2,M|}yNJշX3Le@s?/X F^cW_WCwh}Mء%p.   @\AĹU<8d`&3ܶQЮɅ@e".+ϡC3)Z\kg3eM񩜨csV𱳆=bÆ &#EEk|[Jzzq-{Nھx.֛ra~&ü.{Ï>s,k9?7Y2̳,[%7ij&ifi?$~- L.ϹSfئ wƇw".8q`< @DoݘiժDžu瘯No4-ZY'6hTUtx^LiӦ$l'SR:>529(ѱEǞgS; IUV3w%_viA7xtIJg~9uѯ[5`ܺbrYnj]Mpuفº=w!fe ^2k \_V"{C pdVSLTܳgOkbܼӜ cY>xٶ֌s},֯o]vZ%[5e윷) lF0؟kB<4)UZ>2>ri!+`q͚޳qN.+_5ghV"͚7w^onq`~o%::Z,o^Q"k bשSG:vh1~G#p ~[5&efֲU+ֽ{1~Hl- ^V}7&6V.bDs4n׮̾ -Oޟ7HDAނ~}Cw >#GӲc5jԐ~VDs-Y>lci}Uc   @TADQ)`g1/sl%B@@@@@,=ztDZ5Wo=2s uW͞;8ɓW_qh5W#(sbmg2sfɆ%;wgfzs&y零n.q?q1&Xb Ȩ8j vcZ}ݷz*S26@@@2 $-TZ'dmseCMJM     @ .n'i6t@@zD)czRx9wo)8xP믾̲zcGF^"G Ol I)%s˞8=E@@@@Rj}ìts>;7dJVIܦI ՜!M$M5_nB$ 'ɴie= 6\T 2 .K2A \   ԓmkw\ @AniwtlTڟݕ=aY!17B@@@(|h+:Ng@j"Pn     @@a^Q&@@@@` dX٘}}Jw%lm #         P(D5]: O]ܢfM         @Ml>Su\QUպiii]ؿ_u_   @ do'M$)dn  A^㎓˗ŋpE.    @c d.K tJUڽ1   @pkd.)38īGǺ&*)@t]MMe˖+RV-񦛥IrJAAL,Zirv~OJoQ56l _s6k\F{g͚I;     P"O!@@@7WmTRWnͣ= b2q$涯3kq*pwJ}im$F^{Fky+ip3eSOvҹsgnhrϖnAo^옷 > e    e؟kNiZR DF^A5|zRV>S#Y    T5䶮i޳ gmsM$&V ˋ \Ui{iΔvS%> Ҭ8ZqK{SN)@';[nbNQ z\_r9"7ozKzzG@@Lתmy?x[D\i6 m66Lgmo846Tcѿ!jn'"!   @H*. #`TiݦM; r\}y!4'SRkGo<˓CF^u\p9b ?&6';'   @9YEq*{g۠v#AvA]~j9!@@@@@Z@3shO0Zhҭ{wnּ4mbe{gϖ-+F /@^zs?Y4Kf ⋲tb;oU&$%%I+и^zwei,uMcF-;N蜚jY/ϗO?Drsg8nܸ9'33}Ζ%N'Yn} j

Sտe5!   I%B߂ߑo#C k>ц7"Ow&O <'dStݳ 55ﮌ 0IILL4TekV-[fkbE|)8Ěxob mn <}5/vc=`U ؟@@@(@VSvvr^@MDŽv2@덜$ȩ?FمVZ~[Aׯje5Lfbݙ㜴 e1@'Oy;4wd2ɴi&YuѣXsAa6E;ws޹Vf cdQf<ݦ];ufr^Y̟/3Jon?@@@%[DSD P9+@@@@*R "k#V69y+@Ֆv5]2r\Z?*{~˭Qh3=/kֿ 6Ox} 2+<%ӮIM f2?/X >Pf͘!y2_2y֟^&M ק-[su4mQ\sKHMM-5PG>tL2qQ9B'ć 64wjР-ucb[ڵuifeTBsNLl1p>ҴiS?Ԭt H$jߛq{춾(-{@@@@ dmw-0~5)C 9LQu<peZDש#u/5ԯ_﫜5p7r2Ϝ9S[XuJǿ&6L|Hgi2w'pcBBj,Pܫwo|{@@@-p(ϵOz?q=t׼TquN=r?V DJ9GϵfEx|٧2t&#qΝq/z_L4uݹn@R/xKT[G}jX'ARVqRmJXl*%++K6l--64ƭ\$[EBp_Om$IWn;׽(n}րfWvf@@@ $sCrnIYEI¿ŴA   "`Rn^7ؐiޤ] K /K,67O_~EǷyh`B6u޻fn>\y(ݔN:/uK,^-zγcNJf22sLzl%M7ߢ3^z'؇}׶>e#&&L7&+f, -}-yGO\Ur߿7A֚Y-)   'keJXLB-Pw~"~'ʏ~E     .ȃʄW[&xVZFFd'M|[222۷eUWۋ qkEcsʺ1w9kۯԬYS>+=kv~iy͚5>o央2Lp:uzm$o 󸃕 Y+:UN=4Q Nv={dGS@@@\E*q$ѕ6/r"D ǵz]\ؐ8+}kܦ֗kw,<D B@@@FCm]Vr2X}CɈˇ˚իM ʵd啗ɻ'{Iګ8xs^W) wgL׭35SQs.Z6h>|}yNJշX3L6e@?/X F^cW_WCwhիVeɸ\ @@@@ ڶ\;>)A!.\eD\e:   P,YB!zerO(wf&4b:p5jRo vt{Ӎ7HN%??Ϝ~ϟ:ef U[WV,_.֭{A;mpuہº=w!ҥK+@կ/f퓯Rl<dSOzɡBi@~^P2v(v={JLlZ_yy&ӱ@@@*TЛpq@ ٹتGrZQ x%   @tY6-vVQ_ݙ@dokC&ps\R믾,)A_Cjdggٳtͨ()yޜ97R@@@ zrե#@@@*^Θ]7T#I+    Qu] r "(z쾜Aā9Q @@@r dl>Y SۃDܢ Pfw3'm@@@pDsm*^@D( ޔ !@qDRvnU5+Kr1@@@P@ܸUR°峗ͪV3@rWAbmeIl I)%s˞0!@z YZ4HM6iii]ؿ_t.@@@[^1*[Y$@J؟g3 s    f|"pPgeȉg#C.^xrI    US@yk2B҉sC^nšhUI    P5ŚέU^ًKr,V@@@@Е4iZ]+f6d1_fOM5 ϕUC [)Ae6   /`Op&U? EjEt!a,V]*    @ YM{?IMKǢF d1׷&+f\<""  @âI>;ܐp=fOp5$i}ixTFvBJ37E@@@p 2VtW{uyFG(Byo-@-w@@@v̲vptRsWքب@edw޶7Ls    Pvr¼JlEoMdMb55 iRpA.@@@TI0}bhV 01O D@@@NثO|b5XYAՌD4׊I!((0XHoN {Jh   -px??.|rƅri:{:me    @HJuf>UZ젽US E@}PRJV,/4LhY #ɦ   w7&+Ζ+Wom{M4W;J@@@@<}FMZ,G*@I$V᧑W@NVs?Av`e>m "'   T;dȒVNĞ'"bTڦ9U9Dx @@@@rhWǒc۠ڕoeF_@oY3ggאҬ"8A!xkժ%C],_p6@@@ r rg=)e;X4h929 tM5]{ ٮ .>+x`O&/E5@@@@< 6    ҸU$Y6/M6ovh7@Л4A,hg RU;Ydiѥ{X8QCNr!+G23W_{csi   a#aiS86aJ=ӬM؁   @:E'tӬzKI=$Ĭ/4e>uBr_~@8 yiÇeGy4O>.]gUjW#mڴBRm!:u*vͫFY3fا*hoǚK/o>/v >>A h&5 En:w,o5 ^ZX58&6V@Kgh,/{YZ[vrRWZ^ {i]X/    G#Z<,ime$Nn (>Vsu\K6 zttxf+3vE7rWW5@$ $ǛM7xP,Z3ڮ];;jժ%Mbv} me$d"bުU+W3_K ,I;bkqE5=j&{)w{ZM65\aAaj[ٺe{$ "]hn`d/? >GN],eĸR?X39 /1=Iirl ^>.r%SŸFuV^'`eUg%UZinџ䎉֘%c'oԳ~~\YR]YJ%qXpdYmf-/ A!E,ͺ6)Ry\yyc?m΁Ό:uH_),,w18֭.d$NHH u/HO[d%ѫ^ѤID $n߱Ɏ[>oOp??nZG؍    TQ5Ló23K@RN_` DzTF^;l#    ".zj O<ѣFZYfruq=ݑnkKm۶+~oM ^,:C~}$cSOurrٗ;     [l1>ƚWAr&_z-G.Y4Ğ={]ي3qFaC/;هkZI2 * . #    F b/?[ӷ :a۽{w:qlg߽{zC'm[n$''{ݯ;ǵlx'8/M7i2Kek;FnݼN;c@@@@ȫnLxI=Ӝ`UVzAR$Y_%ܲ=W^h333g\V׷[    8;l{3;#kF:K+x ~xٳ4Q$kd抸89kWSW'קyceY3^f@@@@(F[С<>s>üy}] |aEǕ৞#{ͥu@@@@@ h%͑yWyJy|wkMb_N䩱c%lorf)ͬYN`卷&H̞.'u&N(ו+^zk'# \e8;w6 c'@@@@T lǷo/Oy$sЄ/{Y5kfhv)-*ӰK.n9:s< 1?>f{U0hB3 uhh!.     .Pؔ#fiA6 >korDPNOts['vlx[oʻ'<+'xbzz֩SۦM2j5W^%<7󋝗#^sdMvksv&(Yݟ+m[oש    @l|i=WQ-lܸtu ' c+=X=nϧj2[oI֬Y:w8ve]EN=yw q    -|r ݻwKS333ͱKj\s:?k-_,ܲke˖[=x֭2 T.k6    To2vI^L/O,Uz) 52~ر;N4&Xb$ԫ' dCZ[<{G㎓Zl%[%|G@@@@0fF ]L.g˚m(f.k͛~ ۙ7m(9RK.VW̚9XЯ} yco>|D,^$۷o/w@@@@F7vVp*++W f?~.Wn]S_bZ4ۅ~Q@@@@@J;wNa26eGe;    .P3?      D@IDAT    &@q=@@@@@@@@@@ "QժZ     @X Ԫ]\a.\l    G)GQ^Ӄ/ͲiFܝ) , "     @֬^- $yݲ\hQ@@@@%Pؔ#bkӹd#% C     Lxs`^Z76r8@@@@@q-A@@@@@@@@@@PMU])HSO5Kj_#S|([lٵT}%ys+cwn^2o;pv~r 'Hl\eСC>OUx$9Y @O\-\ PVs~Jc[8c[z|{w^a|@*{|xAI&>Ƈ79[2GNqҺdݺu$@='Kz(͕%ɬY3;ɅCH\l̒^}ʥIa[r5l2ِf#^Y؉@ycWwyiFJ+WNu< @e}YgYä8dZwSƉ_Ylj\rekR>[axeag T89^P/g>"̱8))(8so$O41+՗_Ț5kTޟN?]jԬ)Q 6Z< @8+{%ZU~g|k]5MI9ba9צ(kX9RN =@58W/ 7JÆ =zca/ ew; }mv6o,ζw~͵9'u_M"X޽_:ݳfΔO=龛m,PCY{'Zqg}ܹyAŽ T}r1Sh}䲋y>b|ca|<‹ҾcG]Ҁ˧nj)VQ(PqoR~}-̔1?.K,zN}-tyyC^zyo42NI/2>>~ǝҧo_MdkC$P ֒jC g><|AꛈziRzsdٿ})JXQ𻦤+Jlj_o^iI\Iw88 aG T8-0[lң*ă$PD:c}}Gٳg8) (83ϔ(+Ʉ~:;&(y޿{^:w\r^nپ}1Ɖ ;,.CS}+p?1'  yZ{KfO *Ro[k)ٶnvI\큾@7uIzgYro1lպ i?[oĘsM3csMs=f\+if޽{u]|:ʤ+Z\E'{A'X۰{.WiCk 6?e rqǙimftƇ-ZXWtPQA=>|u+Z4gN:J!S%??9PQA2>lI>寬>ӏű` g\|r8<.{3g"}=r!onA;o;${+ZE_ҳ&5ۄ]'+J \ƇO_{UnOž3>q&uN ~ŪZ / *P~嫿_&=Qwk]GΰH|oe k|?=NZi#OwPjp}VVyMDI'u۷%?\/ 'z-5#dY | _z@8q8sj+ oaƉCF gyzOrbE4!DVVy?P_ShI<4w'_2q)ZY瞷X+vk 32)y{va|(p?{͊k!{݁scG2E hfi?f~ W.6T ,k/~lis:ijY|2Ԫof&kdev9zARZYbo/˭,9[o뤘]?dڠmynw~v@*{| k\jOJ>be>o g||Nga1>l W@e_z|̓o-0>p,a3>lV<\Q_/<\.3>q3Ntu <~٧测n~G8Y tB//D6h`SO_ee}{{"X@ #P)!+P~KwWʶmч9/k. o1}~zsw +RI5VYIUf>.VPq,Zt_;h('8xP=NuMwf~zo*p'f<Ϟ(9W&H-'5 gLDd@߳M 1ہ{*>}8UܻCF^u\p9b sfU ]˯HBBBd|LE 8ܪj ܹSV^-~<| W'YSSSeV&#ĪܹͧsdW^. "##C2wi֧t*#c}2dPdmձ3H,_:'K>sX3_L|K?rͧ507䜧Qtr9o/?9 Xw$n>=z}͠4̧+>;p%ۯ*Z֢Dvafؙ%x\0>kF^kƁ~^"{/5} ghɇ/yY+7n(_kuk9+zrιL9R{iŸ 'v&MW*[O:aba|yᥗg&-Z$۬1дY3Q,^J 3?62Xg><ԳV$ i&%OZ^Po$Ь_J Ɍg|-_|~Vs=d98 p'%㏛,y$ZEEQ\MqRR)UrA}|{}ǒ}qR?\@yƉN3d2 ,a6nVV2e#*vXLݻu{YK雚Z4иd2'ZoS(@8VqAŽ awM_~y0K()Q.Ha|D` gUGJE?zң穦c,[a:!o0NJ**{|8ӽ3f=w!D>3>|ɰOj/鎻VvVM]k>Vt X"DEE/ό}J]ӌ. @ # &*H<79t7}I79 6n虮`go׮98`bG%b|hޙ[+ 8J8  I J @Ylj>Xb(z{}uuS-[ӷzM~l2pBueW=>{=>)E%3>J8\jKChUI@w;*P-3֭^#M657޽{vVkR\KSO)xʖA&f3>q:Nzy\reV}po;=:$::Z4mbؾk<뵆ް'2aeB1>5Gy8/_Z}SWoʫvZ٥k@*sХ vM,}|{N',q3Ϙ$9ηI'+[ T?YA?8ܹC̲V!r/w +S,D | >\' ̊*v>9Cg8GIZzsbcت|,PQ^9Ϋh+ZWK}du͙= n`䵣L>6-Zd7ƏͿo6/H>cg }cw&¦Lu- ,:5)[ cĕWɥÇ{tC'>cyyK1ƇCF% r|'}7=MOO/5S㣒~(#`״j괡A$l9p ye!bJ':QH}/dXxu\!]ٺemI JԩHPQ^9CG|Vt?o6fefɷwsI]!1-ޙlq(بD'c{:8&F &FDOXMD x іh?DV "VP jP]bX?@-ACL 33;{f\Μ9{\wK C'Mz_rꔰQзӳrfJvJ}ˮS =C'ҙ3ګú(>TlpljZy{9c[e{6Y Įz1>"#5AC xoKzҟV=-ඞ oƯ~e|?_xu- 5X@"1U/:Tm-Wx sֳnZ~=sF]eMEsZJQwNWڹsm;}Xw}_vWo'^lY q^偖=N]fLeo󉙟bk/HoH8'3byEF qUB"]><Ϻ5Od]S}oji]p p5>֭]+U*_(i߾|gYgm$ڟ|мEϾg.Sx {|SZ*Fyi_uQ1¾X {|@ؽ;`tԿDXS5[󭲌8 ;N{H[1Կ7pKtrDZʜg;⤩X.2Nx>>ZG 8H@X{}l];KY~ԭW,{Mى@ 5N~I]OY~g3y IOoċ8 sI&~y4'ߪ8Qt ܅q-q^rs؀ݸȔ_GO?*8(X@DOF VI999D*;߷Y}s5޾0NC Q#1>"QX DK]>~6D. Oofd2N >5C>q_4֜}E#@;),E^ھE $r|?"X 4aP?@@~G]yRt7 rz/E C~ z٭fٍ7_Kwk@דN2v^uHyeyB/@R싥@"Gaau{"_f>\] 뮻uHk-Կ1y0HIf/̷~q9'(ǩcߥzI}aqv1>"#R9C+n#➽zsJ=N_:~?9s@cp(mHI6,}1饩I$*?\MP'PuG/~nq1H;H8/$Z[%@P{]N .[E :L +g? @F0sg/;wl߱cGzNda|h%8îzy|]@v,Zߟ X;@$$X]zfܶtWuQձw}Ab[xolxza؅X@"G$}c|D9 @b5['7'W:}1$zjC3NHd'oP?gd' \?דNl X< Ƒ/8Ώu.؄9tR o&$@G-a;YH"Ngv$[92~ʻ3/[,YhsGˍ?L_ u m'\wOf}yf^ @LJ9 K5zAcG0-G[ { ;& ?gtWQ:Mݲ`tSm_IzQLdjKtw0WߍDt%W^~(8z,fy8KM#0C .z,=>"#5A'xni$O:uÍ?+g:eOz;'M2|& ;c(qb1XTPhI_댓`Z쏕@ƉY:n|.~m}"SC|ubız1PoPDAYYYߣ~⋍JMO Q$Н2dtt}μ˜>ɿ8]rcU#g'lb3N؈@G$]c|D9hp Z@ 7Z ĺo|ɅuJ%>s3e_Y'u4n0Ⱦ'S7'_~Qϑ&c}bK˘nnSoޓwOѷڬf8Qw2U͞oI<λb}_ʞW:a $z|,qoH?=/z X suk!_wkr<([/)zLH>cziA\+ݭ߿_y)c?qd褕ToclzmyG}.K#i29C'{F@7#}:FFեw/jҵkWc+uG8@$)`|D9 @b"F{7׿^rfu9QΟ/Feb& oV1/=E'GGk;jcϺ>s8q97v0>JcP:KwHZuHBzFjށF@j_7U:hٯ/_hջoebn~>wu>83./ʳ@s٦΍;ȁ_z|m߾]~5}7V`#vC{<îz,a|p[5Z'T%cL0>R qgyء_b_oլE_CB!zW]z2cH#ċ_MUo~vJP!\la|a? \c#yҽW1esp+f@Yy(X@G[?T?1fW [WemяH8 ow WX $z ^WG=6nxKFytYI 1B q}x޽^ͱNJul˖>w; ĬD0k#39)7ޚNCzrtPKJ,dž a#GkxicSGS:ñ"j|[[:^믾%d~2>ɰ??@?ohNJ.~O;j\pԌxLzMPGIP$P #0>"Q;i;Jeeenpz@222S1RQ*5Q夲 45Ngm~zײqㆰZ8 B@qҒ.0NZǹ$8dzhK߁|m/V81Ohw*iij" =1KS 'M q<ьDl    $@4Fq(!    #͸qjt           @<H"2@@@@@@@@@@@ H"N'           5@@@@@@@@@@H"ɠ)          C$x(s @@@@@@@@@@H$$z2h            8\@@@@@@@@@@$ 8           @<H"2@@@@@@@@@@@ H"N'           5@@@@@@@@@@H"ɠ)          C$x(s @@@@@@@@@@H$$z2h            8\@@@@@@@@@@$ 8           @<H"2@@@@@@@@@@@ H"N'           5@@@@@@@@@@H"ɠ)          C$x(s @@@@@@@@@@H$$z2h            8\@@@@@@@@@@$ 8           @<"\H.8Fzt\{ܶvxGeұSjd O?U,WTp@@@@@ɛJGq" 7ƄU0&L8aF@@@I<:k+ȓ~u}mfW5&ee`kqsHz\QqPrTNvj @@@@@N``ѱbdvj&fZTڍ    #f([K^ 3ѫyoteja޸bg_'߭ YP䎛YY$S%kX.     -usTlU2e.v}Y!fe@@@@ZII *XM4˯qΤx]=gI :?MI^nWxf@@@@h@,㺅 +b>7 W81c    I,@q=92mED_նS~sNpeH)]F%4UUʺis%#W yJ4)hJei"U+dєf?7H]Oae#)յC/_RQ(Lʋ-_-{=C IST{Jmm޽Cl\-+7zbDuTEe"9/ȓS2; Y\=/Wۈ~U)TVj2#JdladSPS)Vވb)˕QN\?^̽'c'JZ::+*T|ƍׄy\k].OPHbî/yJz1uAw5U!R}F\x)Rpfcjb<F*@@@@"Hޭ[qzJHkժNS{F2)#hi{1>QߡoOn!?RָN^ 5`Q YbYMX*F,kZ'S9:hqJr"[.Thwl*sB%C%*B*yuJG$@ϹS6>2KUg>^j8ۻmI^G-k,jyRJN=R#eO$FP*q8g~1UHզd/e@@@8zڧA:\2UDO8TLV2␓yUI)DpjEbݹL51kF?0\Zվ@1+}TMJbA^N/'UvDMQCr;:9e    p D3ng|>펑!?t3`z;k;F;qH鬓LVl= ]"]sCJseOGH쬕:u, 9յ*Y*\pJG[r.SStTsHUSd̢uF gk,yԃJuܯ*\6j8ZE_uu@m- ڶ\1Or{AޚZe}e-S0КÓ@\_#_\Ϸ TMU.Azp/T@j]mzOZ(mA%5^ꮆS%]\u\YX4U:/׵C|xjc;׾e/mOi?u@@@@$pB7-O+~.ywCt6n\W$ I\58;u'1Wizn<:Sn>H$vgq"*Q݌ը@NmK6ڨE ;>d*"*~YSŵ/bƾ}'uT3~noa\xk)n:w;Tu8zG㞢bzoL)QY&Hb6t̾fqk-O&HFYf\qtwX{TcFB>Rh,1O@@@@ $9{}R% 9MXZ 4WFΕ K1c`))پ/$O+B3Se`7lU:E'E"j6; /ʰ 5{oKeY 2]Ycse`>5u$>գf X1Wnߎ(Ӝۊg5x_;Jd-jVVuk<V+oz4)yz\ne\Dt9"Lѣz+eٔwBs yF0ǐ uJ5$ !_lE3_8/*J\_T$v qʖU eʘv"Du@@@@!pƐ1K 95TU{u<\l%-+/O}xJ\XNޢ&&yث DOHl >8_e=ٯ5ʃ*|%QW[l||ܱd2fER7RDUO`D"۬̾t}IqeXaxʹ2)2ĕ|{\yF9[2[<\z֨nYgPΔAʒt{*fD3V׸+XWXDym[֌WUFr{\;kӷ&@@@@+LKxBN2V y"PM%2C:v TF`^{$SWIkJwu,a5Ea]>7qay^L+V[WkVk*UY Q.5[ ^{q%9Wլi^Q=mC/b] ֗(    G_| O!6Ȓ@lzV _ӛc2m8.|̯N>5 ~c3͓S2%7׫ɎM|'Tn7Q.@@@@ DuWhTp-vXABXfXȸaGNxf{/==5!/,Vl=Q!{= MmMWjziSŽq=}zl&GUs`+#Jd}2y`El]_-o&⃇ݧ4|#U~_4 uŖR    G@ȸn gjB%oxm묩[ؚ8ѥF gN:y3wLMؾO?ZC"_@:s}sU56@@@@D$$y"VUkm1Q0"RPPh_ubƄs/ Hacg?L5fH/]- y.1JuweJ^jWWssj_2ts;reLrDLRya3yw{{ *aLy{?o%_38o;"O5/W ˘q2uKb*}RTVZ,9Iz>泍    @kh*hU;8U7ؽBvRX7?m$uםe"c5WŢB 6g 1Oo0f6bKsɰX*& wuČo TӋmW7x8     @R MXN*'=TB#ܾtܮ-*%m:Yf̻!spAԦ-YU.,3ʘ\B׭yzb?zĕ|o>u Go}gG AwSZf[|V"ok3m5RkrA[RDRd8׳j0c8kw.Ӿ$. >Q.m3(WKN.7HZW}34p\    "q]W?,)%k5F6./QX5ӦjlPqRׯv+J)??LF̸͍/s|;ݝ5JĵmGXE@@@h@Jn:RRD]Vj4IL`بbl'R[_#_-/*!Un՞O=yrXSg|j9y#ogF kpк[v >7uD?Xrˠ33Q|j rYĺ`ln@վwvTUAg>O$;L8(@@@@"hgN2  J%H t\lT^nCj*cT|P֪p { M/cy#εRG$3oU1c󤿧@}O j8    @ьD:U;ݢSu˲>     #8Z*0lB0BRUhuK/    ь=bXN(UqX@qn\,ׇUB     L o`u "*xM)     DgX_^.cNWw rkw:뵇 @@@@JTi׾HpT:#O`͚uҾK6:    G8{nFT`Z]VK>ki),      mVN:dq8F%:x_Hگj1Vԋ     @}zQK4Ʃab     1J ֗hӦtɉը@@@@@ 3$08 @@@@8g%Np~,@@@@@x`1TOo4[[4ZD     Q&p!7>,L@@@@@H3ouIe     @vUW% W},     G@bm>Zz    $d[ewq"uOOO@@@@@ G$G[@@@@@ mڴ33q1t:ewM;F;u#-H"R @@@@@DnjC'76JBY@@@@@"HMM=zH۶lUU?۷K5pN׮V;tHKٿoNEqjb@@@@@HjhƌԨFe     Q.c$k||Ft>xca\c$kW1#3S(HD,     W c^3^bZ    $^v~ 鐞.Vu:ؾ}۷;w:x7d     @ D;fcn)3T@@@@@~Т1h2>'y+c'     @M1IĭeD#@@@@@5444H]]_S~ԪY}Ƈ޽{FӁd@@@@@)16;Ԥ{^PG͚yD@@@@}*)855UR۴$\WO%}kZYU%:|㱭:ݳGjvcM˥CFX^@@@@@N 1c͸qJnݚ"Ew 1      -hnTR]]BԐe`q@@@@@ ьma·>$b@c<"    $|NB@@@@@ K niŜ          )r '4M M(lO:nY/           <Z          3 \@@@@@@@@@@#`%'\@@@@@@@@@@-Ď~           K")))          $@II          K"ffQ@@@@@@@@@@V(DV7-VeB@@@@@@@@@@ 6gap!@@@@@@@@@@b&7q̮D           Fqsf#NV@@@@@@@@@@XH"nllND@@@@@@@@@@%`$&g#&u=@@@@@@@@@@XI #          @J"&y?@@@@@@@@@@*q@@@@@@@@@@h~Iz63z          RM\@@@@@@@@@@Fo&bݲԀ4@@@@@@@@@@"-̬Ñr&          .08M@@@@@@@@@@@ r)))^l~mK|     2UYYYM0     @2 D#nlD(ؾL y]&r          ^m⚃}15#ݜ`{q@@@@@#_}zz\;.b     fؘjFas1={#<"          @kH[Ұ 51           DҰ#@@@@@@@@@@@>-N2fA@@@@@@@@@@#zJK@@@@@@@@@@@ -N"nll           TFpD@@@@@@@@@@$*))m"ػ0[           RlK*ֳ'v           @O"n=m           @$bsb]gmv)           DA4@@@@@@@@@@(Vqc/HU           XD          K"NQ76z=           !">o9@@@@@@@@@@@5 D; qk mE@@@@@@@@@@6]$t f(Q@@D d8Dtv[՞\92rNӽ8uR'IڵspT)~dwSv$ow.͗M:yD@@@@Mq)vŀUsn{ٞl͌~{Fɒ%7JvUm7TGߒ@@\+XBLRQ  BL))"gf8)i"{?W,.'wI3!;ſbww٣rIVK9q=]R%s&EϢpҳKsЂi䟚.SM@@@@KJeT8NVϜj dbi9GA*I9ptU  px%  @˙lpHOuM1jdAn쮮UZk:o_03KIۿdRb[ߖzSexbCyS/0wǬ^b]U@@@@Z@sΕA`~48YcRw[өGݲWD&=$ 7^P@@ qVX/o@@HWF /z9gܒ'eU.E92 ̼Qg&ɸ{juۥl#Irns}f/.:;),}RޮKJƮKem}u@@@@|Wc1J VQq@8$└1I >] G(ɷfݶu)CJ ״/~mvjvNY[z,6ߖWVX':ˉ|)2^y,f-?KS_%k,V     @b9FL>٠-  @F@i%  JuYJ4.3=tVE(Q#^S2 S͆Sw%eN2ߝl#    @:F8$>䉠  @ꙇg3fA@hJ S͙#g;ĩ&ygH5֝ꬫW]zZ2K thWN?g|FOXtsjYsn_^Kr7J1:)s}.Rtyq#&fNX&\CӳdgtGQl,gn,RYz#;ۺBύw?S 5oSes{'֠|uR6RlR|rN^}B'L v?UKTboN3Zzس8d2X0jۇR|׭rQO{/2咯_'Z? ΒY䡇.|ojgruP5S=f5DXKL?_NPݱ__w=}vDW_~9[޵S>+ \<@@b+L2:Q9é2  %]n$~g[\S%+83b2Vy(]f>렟ȶL9*H-$9r*qg5Y<$l?@z.^=ynurjCR26琱HO/CY{ϥFlfL>S^Ż??!vɶuQ4K\Եב.ٽϕ9/%݋Ȓk962Z^T_If1:pJOoI93{f;Q'K?_FycUqSm>u2l%)#k    pt 9SMg_KeВkϩvCs͏Qx,g9,}յ.W~7o 6+ΐ~'NH6*AF+5oɣWU]%_.:~&(;s7oϗoUݥ;{OW_,=ӷ$b=E˄EםS6zH7ߠ1t &Cn[>dѹ2VMVfël  @ 5uR%  @ſ{qdN 6ΐ[ޥY${77h_2_fI 6jEJ}^iسvWyĹSk6ѬQT0ʳr-]4û!O x0;;@v`{_@l)oε>*%e5dqs&,y2pOa$    p RZ#;pU@$O|3UfI ?*z6bSnz*A*#ѫmTpb{/%Vsq쁓d Ğ˨5dx %sv7/Ij#ޠ'{͵H .@@ mcY9u# +P'׿ kUri b $LTUD 5{GE/QۦrH5sFe5¾8w'K-wvH50{qqwP6F>|1l*-6>zllqWސ6-͔oUF !EE9?͑wW/ȳ)ߎFm8vkDu̹wF{5z>.=9seo?ϗuj&2bҝv?UnPpth :À.%2TOҴqY#V6\} ۽{vl^tv     /ɪ_ǟ+#fd$~{gE.%+ܾ~LE43ٌ;fZv_~v?5Y}S6@@xz]k @@ |IOyeO4vqidfN`l x    @*P@l7 (9Gr70F q$0;9QW@.̀U:Mos7/tJ .컿Ktznµ>S8V!ҩA:!w^VZBܿJ=>,Mw!/JdMʵ]8R\.X(V#/4_~WR8ye*Gdi)NUNd     ^]Oz^MU#v=zK^z({++I^z{:D    T0yO wSEZ:7gByi! Xi#ދ2Გx2}#JUv. eܿ .X$Q$Ut5Kz)xU/y{rxZ}ho?.qh>-#2JW9nY)~_3MMxD}^(N1s۞_őX,gMeO_PK[[̗U?dIvN V (ܻ^֖.w  ApD|62  %wnԹZە" \|J< J:QGv?/mwH:\l 2IХ \ߕW|}ߵ^2dV*zι[sU>90WQ}_e[vW._N}ǵ:tibɵ1{    D@وX#Y?1ʜdʼe㥪:'ė;gۗL(;3 m2G&l mEqrl{eִΫsՋߟOUT*䔲 "GTa6@@7ر@IDAT $udC@T cOPNu츾qG~ ˴[(7N|UuGQ{~.㹽P%߼FkQs]z*<^6]Z sMTAΥyjʰd䚒du!y;/%I~c}0Uf L[VZ PWep,}Ը#ݶ"uUԙO˘d٦r2UniS^@@@@5RC-5(8#Bnk˵'͐b%#&_ZW뜭|m_+uּ{* w4ټT+Y8Rq~8{4ݽ^6XUVtTuFqm*sӅoߟ֭WOtqA%vmX*%s:[@@d5k(@ĸrYGs2FgC@@VAң:+7]ڢ둥横ٽoPyHɬ]un'C@@ :bsY @@@@@@@,oOJc),ۿ!G@@bfΖqjpku @@@@@ ɦwcFFu:[@@@@@pdܸF-           'U!w*          (?83` @@@@@@@@@@IAf0@@@@@@@@@@B/@q3@@@@@@@@@@@ $@@@@@@@@@@@iqLLg @@@@@@@@@@@v$bۇb@@@@@@@@@@.0@@@@@@@@@@(@q @@@@@@@@@@p 8s@@@@@@@@@@@ A+:ysٻw|tIT!    $Чo_ibl(ׯ@@@@@@Гu.]tgߗ{ 5$fMٱc̙WOMޣ4ha'˖ymkƺRfq[֭[i}KZZݳ$b W@@@@@!CIƍ$qyr p=}㸛mri)^-kr4m*b5eɒH".NJS     ]AM"NHH?<0Uj׮mwlW|J;<;_~sm>5vXiס̙3þ}yf~w~}2tpaL~Ŕ:P͆     Po%&&F%ŋɑ#G*P9Q22 FX]н)qoSBrZzu**r      euJ]W'[iUgu$*ƻw'NSՓgϑz՗Hh<ꫭ]E /2z+Lc-    Tcxw3 ĚPt_6}Ks_u$k{GpvKWRS;!    &$Əv;d\ǁr\yqtLeMg?._9(L^<ҏ*S޶ Uqi] @@@@@j<6]+]()))}IsFL&fqOxVdѕꟋ@@@@@ E`5׌4CWՈDWuL5 x3ȗ;.!^%׭[W;8_=*|qKC-+>Qq녜@@@@@B$puI7븨.c VD\N{կwBw*2aC'Y9͛cҭ{w[<)cNV{x%+    Z (IOIԿy:ܖGe.>?_N0@jj]v]۸\ണӺZcАu%- ~TԩSǥ4խ>ft9wKey2p`tñ7(n$wyKbcjN;s}<0eWcC@@@@AQFrTte~[zꙦU\5;;۶N{6kf9rĥ;}IO613g/>L{J#2]TyO[2ǝ `v@@@@@ ׭duPI&oٰa_dڵkkzW_}%JqLLT#vmڴ]}5Z3UWϧMW] EWЛ!Cv<++۲ <ĴӉudW溌=s֓چWo'Uu }t;q/@@@@@ \N&qqqrgxB#\zZKtO۶-OwUtV*ӑ@s_~$qF M(@1Z]bÏ M<؅@@@@IĽz˯,;>#i~-l% ۷s[IqXz5e_ү9+K,)s~dAӡC䵹scx֏"Ut zx|I34&RG6m{]q7pUIy2YUXf~ӆ!     ;-5SukJ;|sî_evZ]tN3tvWݜ>U\AЉVrhSѠ?NȤ)S.fϖ;*ml۶]FZ$%{ЏPݻT>&* 92!T>'&* 92!T>'}sC-: 2۷oPE***.̠V)v8njk<ͽ}NJJO>%~̙WOMXǃugMyС&`ką- {_{^_z>'qYEpǙQ"[Id?f\>/fs}čEJ+}[zw:URRRLUW DU\_E[^^^EMc$FeXU׹JM ]byۛ'ONutRqAA9~8-pq+Wp%&&J:uB5^rI۶m[Z      PfMo&ު+'UuIĩۍ.pĉr8ԉ/>#yjL&.uRa?:m;Wg+)wto^hܺu >r/PN$ڪ2FllMӢ͛6m*s\8p4iĜ՘u_}H|yٴ[pcWfkޢt+)41]8    $Jăuu+sn2c;NrUbnJJo_@ަGϞ1x3u..\j'$ȅ1uyyoUM' u>7]=JT6fN34cǎ9_fDa Tmɇ҉ϣ\'.\G&?b܍l     @(t¯tq*)n3zڜ[h̜1c5?~~vU1˗i׮];s쬟 ĺ}vҠAC{'obמze9    $JĕY{­s*VeݷvۛCu*>ϟ?$`cJu7n8:oOMTN֛PU6Z,[Y'$7kĽ;}=ɓ ͛7srͨQ.A@@@@"A@zm핢"sMe9Y' ʴv`Ŋ2d֭Izz[sn @@@@@+dߘeY%5GnTxWm;w&:W]-y'Nțoa]&_ce 7s>8+Wt坐UBme۶ɮ]K皊~N'6\^\yxvszG[ \A%]%OM'S?~v# u>{@@@@@ $SL8nAA\ү <_xxڃr)l:^?>}dmdG ?Okպ8\ֽ|gcv7Gc7:6w)$%%I%.m]5&FxE@@@@px#GOW{]gm9}vYf?, ٧]KMTN<)/̙-;v?AU~/%??џo>Y$[:xP֭]k:zS<ԓ`2ty=>^=[vcjܖ-eݿ7i=T̆    Z`zyoƯr$8贇1%t豣x풏>2I oL̜,~Ur˭61U}EENY*V;IjC*휬lorq߾4`KW@@@@G'H ]߿_^}LwkqTr6OLq}e*׎Nr nj'ێ^ېaLea}J?>V@͛2tIUZUݸ|Ѥi3i,S~һ`F;sQ?8      5bjiŨd]kmqqqM\]p#X>/P{iLLu5 ]ou{kMLTev @@@@@ Nb PEEErVOЛ{~):y}&9F̙⾬kfj۶n5ud_Y̫._vQa@@@@"E &Y⨨JDUNyc΍tkTp2~^qoB*кM3-=RG@ TlD =@|N*6|N'd3l[a͵u;رROcNxŗ\"EHD||,^;rP'+'Ѿ: yw*W)!@^E6d86ؓ/o51/ZXaP78r,Wl|z+|lz$     |6W~_*m2&|_     `@ K@@@@@@@@@@c05@@@@@@@@@@ U?5ck?h    DflJK9U2$׌%VcstY.Q胧6N.Gtq,-b驢"q/W8N^i U7 @gDsC7DU>_GC@@@GX[OW\H >}:tH>UYVE*xlm:<ꫭ]:s:YYoe%I     x/ϋN~'Mo_xϽk{S/V[;D˦b @jz"쮾   /;"C@@&`K%INL.xKޚ7GԮ][DZqīȟyFR4~2*:{t! מ Uq)+@@@@@ |x=<_s ѕM"'NL0DgzJz8gS*BԜD@6մ#vLK($X:Q?Q?MVltWf   $n\?ZZ0ܱcGg#*aMsKrrW)t7P_X\\".^d :drҸdGyL5Se&)Gd朜RO)VYlݺ昞C%ve'^ @@@@@ /̙--ݻwUE)u :ח[n&seMFÆR;>e4o.{ztl733Su.uԕ“'w!lTf)hPd0@j3h֑Kɩ1"   -Iz|\Km澆Jq 1XW(n߾cݒѠī`M?"K .pi[o| O͚rwʎeoeGcoQݴI`=8Ώ:$;Gy`ӯuW@@@@@ Tl:w͕ԧk&?ĐϨ''_|<_UVxDf9ps7/K>Pf{`΃΍eo~"   V}"ՓÆEw;v{[]H}O3E'٤$m:_OKq-$66Vq~/.]ү͕Kȵcs9iGWxu+&8&=fY 6_ѿwHZT2~_ԍU62]yɲwӫС~k^3üljk@@@@p8ysg?+oz˖-cǎU8g$'ב)pPk6q/T :yLLWm̟obN*.ukuă}2̜A.Iĝt5 />T#SP syM_Njr~ RF 9j#HYv<NȤ)SuL^=[6j@mvGPZ$%{ZWY.YRit{*L~ jxY|N&j(tꂜC/ 91R]p/=PNU}msϖ9@ 8-,W ɏ>d8vޟREtO>Hl^2g_=571cI֦נ̓ x5WiiibW#WRV$m֬ԑHe]m֦l=How=x<Ќ_pSgl@& LvycۤmωtU|^vn{//Vt&Q[cbb<Ѽy SI}lKD-]*?e+UǦ;}y#1&xͦyUWW aj~4,`00s7)%L( MaJa'$n -퓼'Lh\'$z]֟z]Ny $JH&Uê lMNw{fLNK9rDνb ZӲxBS9XW~gʼ70 fZWՉzx?c%*:u$u֕ÆKrrx:v"7cST_ZzScv=mVqZCۜ9e [[Æ qdz_ZyE@@@@B`~}GdԈƍtcTiyYd;ILxDe:lޢt+)41]8TԦ)-4>!ζk-=# TM$boS{hI^xnO'$]ݝNv֏[>;Mn:fͲu`QzU j*A8AX:/tYѧ'y[V:\G&?4jі7     @ ̜#VM?No0CΝ}ٳL*I: M v fڗeH;خ/Vne  UIqgSnΟ?u"cJͅ{oy=OMT |z}t˖:!Y&e;s<>jPټy95ʹ@@@@@HP*XM|zŊ2d֭D?>Ń{ @ 6K΃I~-lfL@@@.[u.Lꔴ9UuM7BF }d;` sH'oio߶Mv޴Μ9#}:yweyxsN8ps# P'tIyrOHJgpU2   @x1=$M33?VʺkիMbk-LuK]&N㥎)<ҺuѰ<&9/+Vuk>+5Q;/pO--u\vA3ߌ3Fԩ#O?B f/䱧*)onVf`Kugϖݻȇ)U-[Ļo̴N*fCbep    @9/\)N֯_ofѣgOSTSf_W?i8贇DK>z쨉Bt;Kj'Xe>\VZ2]ǃt\ܷiWz%".SaHr~ݟ[d ʞ/`%v\  څ,қΝɂj>Mx51Mnq|;o[rztʕ+\:X:?n;:_揯ϝ@@@@A7|#۶qForC HrZA 9T>!@@p>2fƐF7 fm+C@@@@@\*6$E IUۉeWu:RU5Ue.\   P*`k%axW؂/nҰu{Tr @@@@@@6+?O%'Omĸޟ^GnN^@Ǡ3 f@@ T"߽qǪ5v?-~K@L@:N#|L?:\zdvnSg    P@ؚ~*W@z wkU^69r-i st8  }Q%V-9+'OqtƝQ 5[ GQ@EJ Ƒ@@@-o~Tٹ;x_լq+K ex\5,z 'V*WO V   $=ԓ!#qNA]%+ W( @ta~Q0/@@@R2{+6bkX1XT/at3wHƹRa|   RF(gl@NԦ)vvOT}2f8`p@@@@Xik&]n|*1; vt>+Hݲw昩Gcz3o@Or@@ DB|F q).hSC \)I&Y>\B@@@z $GX׆e2_ Ybf` \GF@Z썮@@Iā'B.Y/s` 7N]=5{kq@@@@ vTM(B >:7@@_@n&}!P*mQ ! R$P36 @ ]U!)-~*3m,, [GceC@Ɛ   `ݱnk)Mij#I9(P:0X8 a~G"^ڄ\) 5oD7S-∸eL@;Dׄ~SY"vWUp EGtNGNU/E@@"`w,*s I j:ʡDFc[$ (/*|u?;#  @D.w"JA 47ejǖ= ;FP7e!  @T mjCbQb,ppip |  @HH")tN $:k*5^/G@+`g<@@@H >1&:,RׅEV]w`|<+8_&(EP Pp+T  Ih'd!>9~g1# @V4-/ٹtܠAaf@@bfumY]ȔIUVlUs_w}(ܑc`Wܵq⹡njT{@@H 8 GV0kN IT[Ǵ@ݤm]uRZT@@@c1Zc$XNt$YL2d*IڕӠ]bߑ&k%%g$?G/U]OSz@@ rH"{L@"H 1`Ɏ5'AY;5"  }}:P>>֟Vp/Ci @@ -" P HJ7%UA @@@R_*u1Ejp/V+RzY, -{eI6yB! @H"=## P);_R Wp   D\zzxWO Vf;wgXTWsvg  $]]q^γn@~Pgi[y@@@3c m\[.*P1"eHuߩ ?Nyk) (`bh@@ 8$ǹڎbm`a%`VIJM zIRY"t   @quĮ_L al ItN2ol A9." rU:F \ٹ y~5D!PR3SY3 @$3$v3t:bY?s = 6 -밁.@@B(@qH7Y#?Dv@*R@j*:׻-?@@@@ X3ПsIri ݪ4+a?ȱTiӟZ2͝꩑߷C `}fN`iѓ@TdcV쒿* ?9>>  $G]eM 3K[GP*ʏ .#PA`a/`2'@@@8,[i,J-˱U:1V'"+{2!k_NXO;/îMD/`U=R\@ u>@IġqgTlp$9 ~    Q*`%޴߬0Y݀_w+ز Վ- ;;1~ӪԒ>ʋ[IvT 0Y >?2έ|U D KUgD@@ @quˬ Xjpa-*V~ 29@u$   P{*oZkYm$).wX@:97B@@ ؒ5`Ε 6D U!B2qE HoeU hpN/R45   vƚoU;x*DC0bUt}O.NwNp `%[v$Ui}yM'i߯eph*Xo&JrsY 8< ̧@@ H"M;ys{ÍrW9a,:VTUdJ?UP})_}_Yi 8'tGf  }oAvbe5'Vt/3T H#T֋+I"V[ $@I_nJթ5w~ :8Ҷ`2|u>ho5py@5Y2D@ =VM6R/E=N+&F=*-͛7WjhE}HLWرC_}uS40'˖Ux}M5W5n*[n1sN\d%G)ڲdHik"~~4Vƹ)r0τ#_@ @@@^=bu$&%~طO;vR8&iۮ:uZbckʧW˻s_M6IOVߚdG~zap٣DbJlU!9g2û V@3t g}e'io`8~ʺ t}lj֟VO@#[ʪ, ؖD#^zŕ19U2f(Yr<˜v !!AE};ota Bkҋ_[q8C/`%Y3X%Q5q$pG3' [$K&Ze["2p""q$VOn\$z[sׯ:^LGWΧzzU!Jq1 WI|OTⲺ^?!W\q#Xj:$vԪukyb,~EozYGٳger sYJz9RO:hVCU4~wQ\?ō V ( @" $ ar 8`|dgs|&9 ` DDP*Jo{CUO+VS]WիWn/lÏ?q;_d]W8ۯ1c]%!Wvh)N!ܐKB @!dB]A@AJN*҆ ocNJGu]zݻ7}Yⶶ6JO_v 'nE,y\64ֻPW}_r]ed `!`"1;}LbG`Vp}$-)jOXULmByc)i؛ϛh >XJlA@GtOYR%-mjjp<}ܳΤ.ܹ _K0\{ut`o}.ϜLo_U]M]yU!R]!e ZnV2:lY!fg4ntbq-0.$KD1    'xNoֲ6|~N=y>=7yԷo߂|^K_릹N?.S>5f1fX: ҒG@_F~9A TMi4٦zk}߾9s =nMX`%( znxLҩwΰ"[[y&&[+}fVutqdݤSOu3"{/Nɀ  hz_ϟ{m޼ &MDvzV0%9slK,HheC7/~k 1 nݚ^腭 Gu4=|2 8ɶ5zzGk9nYQ^ſ.]N:*m$oܴ٬ ψ!vxԳGJP@W/I֪ٽ`y66_Oe&ӮN7]S4QjBvTn%.A@A '̾aΙ6yd?q"}ws,2mF'XF +?r 1={܅H@Seoȃ9j~l|0ӗvC'dz? EN;8vܐkIÍ{ge U1nY uɪrOhQHh߬@a+\V;l񵦮>LЦjgV!XJʥ|%%M[,miq ~ =c0Updޗr4d  0D|)-[(mrⰀv|SeM AJl8q\W%bPN ?kYe~9" mmmĖ01osͣ^w}N#FؿGC'Νgchl{I[~d[`vYBvǎPˎbVng66t= HGDlUZ    Xʖ YC l-[ݭN!3fdY>|= ,#2dBtXhŵA|Bټ6s (D7X u-͙["EVݓQ߁ g;O.>p3)^p2VU+~ݫ2c 7'\O| lp)X 6Dcؘ\  Gc`PUH?)eϟw>X|Ƚb-t`( 7|2b+afٓN=.$=۾};=]y0/s7͟{"}y Y,ñ /?`+w‹.2Ǝgu/}:ٲQl7Jt E` dYEPJLɘ   @!0r(KX>nukFQ+LBݻwe¯~2c]:Vl^kv`C<@wo̞A`aȃYF6z>䐲su;(=^&mZ&gBF7&qу\ Iq6ll-M%b&,&Ź?3fҍ1+7FN&-Pݒl&E3N+^]_~%v+3{u4xЬe˖Uquu(%֕}^JŻn!7#4ܫ?nMT~3 ʪ*깫NbԲ,܁ƙVOG[V巨ƕwoRq+>XgvuuuYtFa]t~ |I)M+x=;nGpҦ} '7A@/N)Ji'.צ??qD/|>waOn.ߜ3F[aTqܜa~[FjjjݚUl~G;--3fy0+qX=lnUZkC;tg Y׽ >忕a = S'}(ZV_9_fBFGy-*Hx%aqe6c+p?>kTДύ? EmM쩀63Oh]@]+5j"k}_in2Tr{7PG.ϡ;WmI>ڋu{20aA=GUFuB'6ٵo3 76d_gmJxLgN] U~v>w~A}Ҕgy"W{)Ot%iD+]IK+"7 |:g|,usFK'r'O~:WFݨ{= گj%x' lY~K5ՙIWk.b_c;3 ,$'/y`QRӧmjawGZ߿K\:#$v㢧Aڜ^%*+NG=ߞNh.?]| !P;(a|^ӱ:DEǻsJM! Ex /āVSo%`f&œ8m)~ ֏kn\0+u?'4imSO)OIYV4fʅ͛vYJĕe=*yhӰaw`LJ鬳ϱp?!c\Js%.OaΓ,Kl G,XChMQv; Nsr6۝;pEn*iԱmJkh V됁2M;t_tQ8fב>m+_h42u~mh[6l#x-2/7/*4Qf9`a4GKMxi'q#.i'i,59NJ},c~ ']|9g|i< JXo'QFY۴̍S:7V eۚڵks uY}*xdfAm?d\1_hFyK'}ȅ! xd~ف?fr{y駱It!iYb_ow[w@HQ^Fꢚ^1-o&Mk&i}勴>Q?8ޘnZEۛ#o>VE[M uu` 5ԺUk=D_}E{A ֭U 6*medK^x| ͏[SӪ-RۋWf6Pߑ?w_?ZwӋ-J*LW4ԽvMzA+ICʝAydC;*ށۼY6HWmWy42ݦm:/F@XyAٿK;IJ CO<=`wm?~_(NϋO:š&/1ioԷo_:iT[Y/u;ow J5 XuXoV$vsP 3N?5nt@Yv0oiJ"ƛzSi#@H,|fW7ɕp*^f7O  ʕ+G ߣN?|M;ls͛={37O|Tq7j}iz ;wtI셍.}225,ŒVRJU&ܽ fKa;M_D;o8v@;<{+ue^\q +tL&/so@AzM}U_|sZRgL [>4hDn8ӔA@A@ЅQ%b/&iK^.i{b ZgCf̠=2} .feWfNEVVUG̢ٟ [.eׯcda[:7YV4֊{[ uZ7UBQ-ZOJٗ~bz]oӐ!CaK捧zQ@+bMU^TH]&F2 ̦)7~MpT0o>2$u(B<9t8A@A@ oaVɷs/vXV">\I@bazf+N&by< }P&-+|N_JB).P!eru~Qsu|g M`l´&vc#cˆOCASnCsGT]qKMxnt#:˛  3EQ"8+nۺՎ2ReӍ0^p~~<hԨQv]Mt;j]a|{ˡOLXJ?=8A@L {bG)@9sO2p{{{+~K_>'/otX߭ο [=U--;譎kr|2lc>bwaݓ /$X]^>cB V3N;WO8q. P۶ygkY%۶wź;T,v|: ^۶ww]6|ǎjJG~UG#9<~X]z7a7q$<fS.`'6~uHWΖeb e_!`ME1u3(uI*ɇI7g,o@3p ;3Sbe܊&wDVfi CtC=ƙR˟GA@ #J߽:zw/5++,w}[]FRb˻|5ܟL6߽z:Dz|_5_l<3 rmҤItNlݢQkk.X`oD/~~}_8Nof;M}xMlI_=Ӷ0+8X83pnjwoƖKy]# lݵͬAC=5ɋ>+5V=u+ǁ7`MT8PK?N\tZ @5uqCx0> ]< 79@:@H:.A@H?.=ڲ<0!3fMy3ԧl?~g9N; Ԓ+sXz~e9;t1ؿO>唬LeO<8zBr[,Y-sٝt]A)ʫȣ?<(b*B(B YY7Ny68w _DQ&5bU2JbPBD߼`Ut7i Eh{}C%kMr8t  @t2%BֶVر𗯑SO=@֫:uK|BgMqF=|?K+|9Y%޿2՝|p`A󳖅|?{9bt:@+**>=|1lF:lelFlݲ?_J/2OiB`9sb|EkCeqPutZpNjn&Yœ@h:{Wi? D@{b?i>Z- qI$YɴvK#lW@L@"dߝ} Z Y',#aÆv|2M_{VZegW^Rf25σ, ĬXF&+sd/"=?᧞|?D(UW_M_'08ͱX ge>|{S?B>C,X֢᧦U|fW0 #yPem#RNt!wOz{Mܿpa+3[mn4`[t#Ce6is/ƨ-% 7 <*m\KM^~m܂@H`5s%b)NUpmjjC_(cQZwvZ^$WGwuUz.}[!ucϖ{_5Ν;UXaE ~g'+Q? ߻KC; Hÿx>Ulb |r^Y+*KiĢ,ق4wZUi0Q@X(2~m,L#yκ5UhvKZ5i;|^9» >- +@tekbY\ Y^yeb+SGuNtI%`{p!ϫ–7v'*?{fh!͖5eK?kkЅ}'xWEn s lME!sI' #1[MvcGYr[<7?ozaڂALݶa2өN~hޠyA@EM(8돼CSԹoqXkMu_ూ 3uH7&Š/{9g~8A@(mxcƧmrs''l3sD_O׿]%nYX9x9N[!/G`?cLzMZt+=/K/'k|2{q]Ygy:mP%g ˱Eo1thuݞTW_G/ m\>'+;Cx̙TU]M֟tr =j~iX)~NS4ԑ4^ʚ*ńn;&b$ʥCۤP_%K\Nto@!A@4 5($puvȑFѨ#ƺɎ4׿`9UW\N3>xb;b,O]wґGM{'ܕO}gakW ee- :uec7׏~I"7XO&dl+9Lz7g(ky|B|;~7 ,XNP3>t*&BAMrn2'7G&E{wK?4)k\t:NJ=횜a(ע4.C,s5eu|;|J:b%   t"`D7oLlد-{OO_DP=\k.HXn~TSSt1 quJyмsq_yyicȵI$ƷY.5:Dm(?2)2 @U?R ~oMϱ\} ƾun6m[sypap]K-4NJi).GZh"G(r3+9V@8>+ye]|`rї0 q)Gym"Iٜnڼf 8Xhm2tOפC:H#mOwڟkBWNEq^fk܁10ZM EC@A/ nj@ӓH> t$& Dnߵ@,87u^n)АMR(Eyau'>lNSDctcbJ1]2,}V8 M=A@A@H-YkڭCZgVX7|dXbVpo0ssA~&_81_iƖ6e8UpHm?T1~ĕc-R}<{LsutIo mA@H.rCeF䕉pdQ"6Pؾ9#w^tB(WSJXp4c$`(z S'3dB`4+]'bc"tvLx)qlUlFNkQ+0T>% PF4B_A@ #&{"T~԰K3ȴȠT-L)a=kūkl!?Ϗf(ܩrBQ'U66y*ζ!:UܹnQqG=[AQ73.~a#HEZk®)VƦJ+چui&Ҙ|xWcKyxr[L`a&fܴ|LcmD4u=𓚖w 1!w55Z2q1mh`e" )CԼ:y9MG1N8ʱp#rY|9ymڵ4Y)(-RwOS Wq6 gK'_pU|>-(R q;8 w(VB`_N>E d/?^XH#u]D:Zh:;:ңv[z% 9A@A}ז._;˗Rs61w .pjOZHסcҾC7d>v .4x0Pw|wV&Y;hᡠa lpx 0#V VₛPf#j߃( Ɨ!niJ7Ӑ8Iı߫ {t:}"8#"+fPQ1٣mM;o4 ' @ c7+ ?G%u0Lx@KxXc!`}Bs  yDD <)K 4o!ZSA@IDAT&qDl X;:D7KLy@R~MsD+ wڬ3w$wuz  1CW^~y((f fh(pg?MZ3B"Vܮ_"^|P~z2%k{e\'.s7%a\MLB"Am2G7B̟-ᾳ`y3=J&mȰRQ⃲iY:(ew\}3؛QU؟S]7N~0i^I~w s!\OrP/ U[熍B_]vڥDcMg0L'µ D7U$0QE"8YXPӖr+0"GHB ]r.qM`B]uN̮N 4e~#dr`Fxn MQE Mm4h„&[ gx/7X3b;/ K@VCNCV/a6Ic* uv[{}`+(ˊ#q`0!A F<ĉQT㎯*ϣ;_XP|e-Ja-)ED@]#.IK""YoLS !ka˛_Γ}XЩ&H\V`:SN[\ M&,WӍ&,i61Vai pӅ@b[Ӟ_RAn(jW>F4%׆. Pb%KF8e6UbB b<6@7An 9fT+Ҏbq7yE'_1kqD;a)s&x@9"P30>˲M SiNLeg*PN=P790¸ETY) ! Jnp"tTABh1;Cƹ1UW T3w{=?SnGL1A?}PwZ1P&O2LZ7%x=V8B׉!ҋ́p.@i"E-SáOX86Ntqӎy:ah#,Ɓ4LA}gFRGi@]hݲ;*)(@7ْ C&fM,cIm uG\u!K¦(}=/"&rg]~| lpDBS!;k2P.Cp"Nwmk2VtO 11 !S+q!nI=E=Ï0Q~# 6S ~ Qy+ҔisTy=tųh¦G7E h҉ ˌ;u5Hz ӧ_{\A@(AL`#-:VV{ȅL`S4tH?"Lnk%2` J&vO ny>{qC1&ȰZ/&pa-c ĘzYFJmnViӗ//Q2( Pq}2[Ǽp ƒ607^:N^ s }.Ϋ*O(+ ج W:p?50ĸ6=yl*usWHLB̨iˌJ"OU <_1C 3XoBq(>4](rN7A1Bno:?2^۰{P 2 "L&8+PvkqPNU1ZП.3ܶkj_}<||-C֭~>l6^yWaZxԽ8?ׇz>)8%cUa†Z/l|t SׁHLA >D8>%@,t*n` ݒ! I$0.t[ŷv7VaWSJOAO-TҠg[76L QX'ч*̋ ސF4*bcc_2Udr–Ϗ,&F2%8I(A@!f&f*>F;~AWC񺕩ׁOn;(!uv׋(iVzT`! &΍َ0>@`떮NI*s؃V9ʳi y(V˯%ǂ taL'4A@(WΧfRUiۣq:&7ڱ+nO {X[16%EjaADS 24b; GG?aLCB^&YԹiWg+t^qc~&% a::i Bs +-恘#'DKlK*g4a.9Y/Rх7&J~'j8HtOU_=OqŤEũX8`R :XN]v:xFP 3lwU\Ol:JY_tt$:P:HO‹ $T~7_^3s IǻfZA @H+"P`hpr(pANֲiW2EE 8WdCA Tb~ ›WXXQ2!c,r=/`EVUE/>cY'7[y՞򡆛qd[K+wǍ6/uL7~yX7417\`wK@8_ݩ st`3 Fg-]u7ua:ӊk,/%AsA |ZBȆ{. &̪ =Lu&m5NELbqRPPtT5L߱)t>MVJX5c4F1q}]1.aL]'l߅3a) cxncmQA Rv\|G@"QT7Ƭ94(ESA @ Z!I.qL¤nԵssMZ1sް^xUa!G7m_qIXzˣ:POm-l' s|M+iݦ S?g웭kٙ`?#_75*/J8<8&/&yg~xYL8uK ~W1թuQިw 0C@ú,RN4_,t-E:P5*H.~¤qSǴ*l^4G0"_X&w,\MK^^] m@wpG']7q g:y.Z()0#?Q]ێW7:LOA)@i#Y.g`p䅀ү.BH{nH)2󎃶PՕ4&硋QnaJ)B dMjRҭLюȣ\0\//aqGtE#Mi,|(ۿYkEq@!*?X3![%bs e ]Lڭ첓|k覑t <} &!Xܪ?,`CA4ϦdY+9t)BT ,i:ajBaՍSG D@m5uUY]] d87oL+W]s|a~8AX~$ G)OD?.$EA@A<0`ͧzUÚqYiPX*&(X;s]%mXWۯCgRs5(NGbܸ H^ DB6KCu[EU]Ig_?o+՚?NUc4QA|HmmD@Pid2q;iq&;QU敥$+T=SV/V  Q"ց ޶ QK6(aptNaM. ĝyv tK'H~ƇNx,`K&+1Fib,I~FXΛYQ7oGx⣎`O]cPyhfw|<*zBg;͇yY֚!gLqMk"eL sG>a5n[>T/y׀(kQHEfS3Oi̧< -8VF (aBy:Pfdk"++fӜδB8  O׺0(O}"LDD/`:y`Q<kJq1 M軂OBxqi;=*3W%K:9.i0&I\g:B_ -Wa[c? @i!CVfԹ .PtD\O'YaFYS$Ǵ{92[l ˞x 5v8_}E6Žn0N0` QOʊA~k`:G y(4uą|F-6~O;ڧ5/<5&~9cL1#/X\ Ƭ K1o3 ާ>Ʉ 0V"":+ ?LSesNկTߝz|&2{NMފtZƗ_7'OG@#_Mt V(pϓ7 DEL(`*X<I2"2-5,xAV|Ln(OoԼMޣ񱐁)ΘCGJ"6haDD K'H5O2Vc-PHBeb΂ 5lWV٤ZN:~5=d="bD% `ܼ  A-(Hܸ²|k/θxo8CU~0R¾;-CIY&` ׺[˧3^M}f{hN!&6I Z;3 N|D|ww_Z֮蓙{G@68̐'|LtDt ,PhIz0t0V=|@@s=::hI]/'`!aA%*ϒ &̦( B*@a֏ \:y$C0&(R\(8cg6pNJu5.!쩋TL蹽oIK{W3q.>dlvhC&ڿ6X$ YQ"{؜3鸄2((.қ}L:UT$ .`]]hAd:icIlP! +!ɱ P&T±2.돼KB'U<. ΃.P) $ YsX܃]=I7/\ m | }lشʷ=x5OAQ_-RyR VPF̒M1^14[2v;oAvд=U^c/.r@iQ { 7a 2.e~8Q̡qQw _"+&wԗr-$h9KVlsʧpbdPINAtTΒ\@O*ā &J"m3Θy eSRIG{kQ7Wj܇M2j.B90fIDPdE4lF$6(吅iK>6IJ4$W@4fӥgzWE`r2dEܩs]@?J/@( 8In\1'# |BȯkA/5l1ߑ_?*/ ŷ0OVR0āZuȷ/%jX`>>,=`5,KN0 Og%E]10N鐹o'-t3ݣM2y)L^Ԅ'-0_t`E:k: :򃅫m#,ƜNV$+c22?uEw9fd_9\yA)`"/D ՑFy_@s>iM YֵoAe Ʊ8. .T YS'źT@]hҩ7 u,Un߸ ȉM8i !M$8$N)E!myl2,MYif)sPnL$1 ojyFr8 [~AuQϧ<Գj$"yMQyV :-BkG{tx0,8"cŌK\8~aaTMaAX lozڊo29VCmV9뷩:o>u2k#c0:0Aa8y0-B4~չLj[%M[1ŭe+&NiO>`|Լ^;kdΟiT@yrߒuDނ+G3+:d 1s&eߺe =Ѓz*%T#<pOgPkkg|84hPd[KK =`>y>{A>8#wtRZtJ3O{ґ-X08l``#B!Y P YȊY> BaU+dҬ <)~t"A}'jM8?&x l`Ek[90BOگ[)uY? ȿ)J lЯQ=" ,e=4p^4*=Fi89l qB&zU9lMy9=_^K0-xõ?oIE`;ЕW]mAO?k9KˏM#e ˛"щ+2d4b(Aٶ ^D0?v*X^WL=q6obRsr1QRq6!Ehw }" ]``J7dsr.VZ;|L1~NJ :Шk%//4NBo;Za?Kxc-cܝ}.X~D۫=ș-|7d>T:sәm͝_U-4ywҘ} {, B`8oHByHJ&h'uDRSl>)q϶b3'ΛG.| ',淾EUծO>֖sy;Ohܻw/]{=|v}?347&3e{{0Aw@@ar:h(쑳NuM^Q Efsgr&LX 0IaVn TWGVM_G&NJj:a4! >J8;̣-a/t '6N<#`{*ϽsW1_E1_\޴;uQnO8d8ηCyI1rD,A1З4 /+tgN.Y?sw>HwyGonN/wl^[6oF"0`@}wV›U<4 B ^ 3CkMlY6-p7–)!FWIi4aXbg^jۚ0Q,/eL% me86ba}tJ.ӎڑR2 LzEL:FE I: hP^c=OWؿsUÄygcQ:_0&yWx| ،0Y{5ݤs]lzw>]-l $9q -^jCj?^Ƥ>ߔu4.o<4&-;Ͽ>Lr c鶉Ċ #]j5νL/tӓMWJ[N?߳ Ĭgݺu>{SiksRm X|듕;]arÀܮcJ[qS׽`ܶl0R 04I0OL]6|Q O HVx6;my>PSXv"/ )xTޡ})7( c/Oέݚʳ9Xa KE}A9s~1Ҏ3^i 0A1~Zb7 %?HܣxHq p 7YgU m)nذ8v,=㬳3ΖV۰DD1rt6^1Y~dÌmxjiE Eas"j]Xwϩę跌rbW(<֞0c*][U 7Hae} p@߂C>Ҵ+oT9\G`[JX(_(Q}=SW1M)P;(_G}`/ũ`-ڠ9NSw9}F(U()Gk".d?tx،9FA::{Ӽ.7A޹~=&:_~sH;.s~D'l088u ='8~GU )C,VpuC;Ęg' VF:o~nuEw׃˪/ h:O>I?Utq}¯B>>VZ77,@&Ks=K?6oh>Wȃ;RhVwaq]]My#V N9LġZqccr5N˜`;?hf֯&ٍlqˆ_4L|\aRcazVy'M'<{2 :7\1{/R'nJdzew݇ 0Uh}m'YYHnzg0$Rid>ͼkU+I8l&+?{ğ'o6| [䯸*:fK܍N=4.+OrTVW>l[ozӖ9>n+dX _nPLZMjR$,䒤h\T!Tp sukJ$61FyuC{xM1[hoV۴LJ| &+ǼNla(#794ʂ߃Z۶4d~w5ύiVdf?נmZÈ"> : e~AG}'N8 .}D+iBQyhF+ba;ϟ&-i[&`3w __b?b_<sys_;w^awXmonƬyk[nz'vxgw5#qD.H刚2VƇ?3L tF.Fz-k=KXuYWҍԧƎSPxQT[[ki,SWGGu4=*y'Zl@eҤI4ɶ5zzG=h ślA=[_8u@.- 5quEUtcaSag/-#RGuXH:7at8j~¤Oi |r-Vy#^t)AĖIқ&M-rd35uUP'ICX_1y,K`gmC ^"Lk#SM8g2 ]0tKI_~y}e3Owiر4bӷ/ӧwmY*+o߶Se޴rlF*7hllь}Rۮ]ODL*;io Q7n?+lǍRsuM ( v4m)½simmAIx`din3R_f7's ?c1O@ ixe5p o0'w?HOX?a`iH̹ո8 5Rêﬠl)~@0OQ}ZL3ϘZ ܋~PGY;QyV'VTIu HI1g =Gf~ -]7xpReEuU"f &Li^L-ac 7F~A9Ac>JZVUKEǝpXR~N8qmqa;zwb(3߷ҙAZQP@5,3 m'0a겐El!4u!B@up>ndwpG;'u>q7'dnjX{ YʧF#GM(@|7FMPb=&ey{࿛p2^PUD;b~T=m{h׶@$k(c+CIَ졍xҫ75YJa[af[P.a,cpOi$_zq +ON>Թ}[/iOΞ]: V>`xWk#*_Q?kKm cPzrov(eySx;ٽړu;__* 6Hmi-&z̲_N%k3x]cfn8[]*(C`3uKk4'+fc6m,Gd7nb NwPee%qZ~Yy7c @F;wU|sͣ^w}NtneĹl`|#?q"}}9i|ˏhʴiY]u ǎjh@-;vؿ@Df3T<`c xbA> ~&[B"NKVBߑ R%;B Kqiuz):sAҎ1v}qi JKZU7sN}AZN#rۜ1׆UƕA*m0= d[dd!A(-KaΝᱍD-3 ,{lbᗍB@㏧k^N:$f,Sܤ";es PCΜB3Μ"J aTo?3nbslLyn^YwW;^cy"_ %pmSA踅8ڱ[B~'Ci^oA{>AQrJv:/~oӼ/!z2 KmmIg14RG=Piiڕ 6kB._&l]5ۯ:qh?aEϤl\OuVK?'u¾P~t<)Ǽct4<th|`!6i.+VgCE͖wvKc73nc!JW'-o'v%⚚:;—_.aGn+vޝnϛ;gtC+H+>gٳ'z]={}vz駻|w`s7͟{"}y Y,ñ /?@nAtEC lK_N,)=8ڲҋ/V׭[g) #P,hf=h87)@`3 u 8;1vEMl8 Kȇa6H3 )~nApe,vnhܯAcrq\5nrA_&~x0($`کåiҙb wXk3^m˵x M6DtW􂶉rxС+G[7mݚʇ?}s/g_ۊa Y M6߽֖KiO{2+C2?Q |!$Z 0,'(9b=) b*R<(d| S+_w5'+V̲.Vh(l\qD ך/hcUxs W3VvIjX)Kt 9T׻|a~{Z~c8q6x%?`[ YT#o u}'eo73U8CFȏIK( i}b/?΃y|?ƙYP$uXa|X8E(5I'̺%L:G-ٰПͱ9Wtl{e/zxA]ceMvHEZWͮ4xb9K>օX8]q+>w2Qڌ>+mGsG {PK'_<|aM|cK欐z6 detŘ:+3h=ښd.޷o_ `]5qzt7.E/{= i1kg:@ˢ f./,P߻׻s^y+ByٶŊl_zwO?$jIW^ss;Cf-/[VʜL:sohhhOv&2WvT|RmӳuWEUqCh󸌕R{wjt ЮŷWC ?zp{!8o~ҟڗi㩽0WUVUg{Zq]oN}~x弫nP 9QZ&XPg>_uXl[ڙG=-OlM0i͔+\^*?D#~ 2n@ֻ_MؘoUꍊyv,KN3*mbm]=v}oL〃&Y}iؠ]c[eڱﰴ3)Z?ywV=T pE24U|6P&Sύwg;pnNxp8媣5@IDATsWFqS.*]7pq Z_P/u=y=mDȇoozY;3'.UއB(o/ГWJ3wmOt4{N9w_z?`ϙF[VjD<7d~|[h-I*u~M9lo1rJw?!f%c_yI'%V ^+>_GHݛ29qξ?c ۧe?%P^9\̺aD 9|s`-u*#^y4dp@[5V:Na,|,/xG ]q;h˩j Ս]p~rXgC\q扙;JCogAi4ȴIAY]g>}aIJsԅ*i ?/埿aAPdݜ탞.̘?o$Z{,(So.|#Lz~<~̃t^O`bs'=og6 Ϣ iSk W;و/=H?PRgf-ێ9@?}7waMAMct9,sNKm$n=^ 絆[Qh)kNa8uϙ([=>0 oyq:|;c`UMfMcrGAqQyGYsrw?0ƙsV}cFS{9pB?mo-+Z*_UZ5p`LzC-7]v~ 'qe `;U7d3[*^]gسs oNXOF}6#3}'tj<}3wlϬ'GOs5 ?5~LCsW Ϳ\xyI;LݐL@5LS!Cl0s@ |ȍte_A'gN.Y~[:9+VEYKghX2 A*]?un{e:A_JO-M~K5ՙϮ:}r0+:+ܙQtm 槟| 3 QY6uX(>?-vT]NGǞք6jd&|ݗ в)s};i.x̮ ڿ^yoi :{:ʪua~"}UXG^_ЯQЎB+./ m{hۚAů?˶5)m(zTUv/s*鯻c+wu۵=`@߂>BM[;0E/t5fFmOs@E dTzŎ1ZEK{<D-n#Kq}{<őBq/ |aܾOh~\y^67x-wcx^xh[wqȚaU'0'叹wlQG9-s0ywnZ8qzv ۩_BWz͏c,/f l)K&lbG֭3g]C./2#zꩧ[n~w<+u{wf[`1)sbwe$C8<ƱX/loAdnrU c^^h7Dmz.k~EF y~+x r`?4\oxp畎Vbwmj̆.*\%\VpAsy>Co^qTsʺі*$0 jQOwЧ[ja}D{R3>GIia;/dպqQw_;+ƞ%t|Ge':n't͎>ڐ1β#ZIe{aՖ"%9 9?_>?xxG=nAsϮx҇w㡃s3Iv{3d^_c9C86/^?s`V0u9~|O=c9c%yT=;5@jnnniA ԻIcL Kն8b7Vj--h?T.}}jin!4ѦRgeRLlT7m UYRi*ZemЬ[I >$E;-V106ޜ+A*Ov]\rs Q'<~M]m6A]owF w?tU,6kd+nuk zL]{wiR6ۚG-qXm8Lz/>&}h7[YԴj M;ަv(tmBe6'o'w^e,RskfHm0,k\{`jiy"KO8nC7vח~F4Q_V/]g3r %Gu l$%ai"lc1`cMOnlmxr6o;Վ۫fȚ#ks߹h(ь/?9uSnnnG5DޅG}k3(۝3`͟]3ܲr}2] Їy)0gӇv#v>c}1:pɆpkTt]q`rcdV"e/.Gybo~?j1O}SRk_Տd=K??芴jಏ>!uwC뙬Ψ\ܓ7T`ܢv8sh{k&q&iQ91`rQK2֕SA> i_,{$6њMf _wm+x `ʇuJ;4*t_sa9O^k~h=ˆ~1osd0Ou+[]?tNxܙϠ:SҧƐ5xGzFöRZoV-?I͙ /_nx3?҉O_'/οe^o=YƎ(7|."Oví7{Y8 vEfn8oлoזּɆ=?ǫFlnjJA>C*V:ӣ+<1,r:9Grql^?R۪,Kr =:vݚ/:Hz8@|衇V31`{[;ҍ<'xbS?_T^ta ^`ٽ}ڹ^Nn;u{+4gų񙍣t +Ω'o4GEqOo/ oss/N_nM{Eڂ]8c]rV]uY?9J#蹧 hO?_GtPsyg|T?tD}oM|"ǟo]|j|z6]6㣧3'w_ҷ\To;}C⿔rT}򏿰 ݥozVw],9h?g:]Kpp_r*$>SN'$__Y;o/ikq˕#&~sV1OH?:+nj>fƟ縴1'ѥ)Vs=/:5S MRS.O^괠{yzG3ԮSCYzfG߼B17˼oE&}}pg4>xsvoӌ\~Y+}cBcK_Gҁ}g]>vj{Ɩ'm[4 uKiqtyIЗF-tIK& c@&6al_n/90%Wgi?|zmK;{u=q .CXAd#?v֓ؖQvP՜Pų90C:@WʕMO7}5?y~xg+u{0?w+oxT}@Mn9(C_W?ꍿ_Bv9ꨣV't p5pt5۳u,Ln&ε[C HS\`>u>/J#1c~:7Xاp8BG?&w!8;E捓|Zu?nwp_g=q0|hCl-,0m, gJa=7G8S_VK|Qv6ԡt, ?`N&5U">(E~8ĺA۫W+o .m~N.f8KUwmno -om.!x@;?b/r}9ǢnJ*LJO,0lxO;k7.ځ&։.&^B+'vZe͛ԞUi\ S{cו16 2}v]Tomx>C,RuFx5Zv3vP<*'+_>N=Ts}r _E$#]91ߙM&]| 4T]F_8?ÞATT:O}ga@!yիumo~1|._[ݥesWe/8㌦UW]9c;:aV=1/P80rHyOj:SSG4-o~W^{mxW/|9Sp* O+_D*7`sL2Ɏ^;i#'s Y  Yͳ<1 ˍ;l> mto#p e}i[%i\ 8 4 S7k*ekQс3TڻkVԷBى:US^t^u*X^O|BK11ź@%q60e%K`@v`T8J>󜋞ؠm]mn )^͚'Vt|։դabq;F;8z۶Uy+G?@,>?M(_W_]OS~'c_{oF||b}Fw YWj>ˤ9f0; IDڐM}TJm|k\n6}scsC9Y=?67߂|_tp/,\es޺(1tT>ٗnwmϓ6֦~:ewzuZ my CȑϣcIHW1MbqTJ[355vPYs!>Æ$?$O[wlM|^8yCak|"'^w.Oi(">%v4e5U=eh/ůO #ENR {WsPBxqO]V62l[p<49l3R}LF/pn7ƇѤe>_R7Zthr=҃Q>V98d3;2n9ІcF/Ҥ}^OG6χx~Rí_ ?o}xϿڸqc+u?%K>u}nCķ:싃76xt#~nN7'oVٖûϿGWs퓛,HyRsW=v[9HW wМWj*#&YRf|+7/3F| ;Jʭ7,kE}B阕M-ر>2=gd> ly8y9qX.bhSp膾~ƒc,Xs8VC0fm9鬉m?Gvמnw̶vZMb;w@kNals]@v?)G6oaj\EJqg}?'џrDJ`O?g9l9$%3ӯֈ0n@^\б8_';׼տrS9p 9ۼ>ծ]~ה{꾥9[¬?bz+3?0kZ3GԾ7!˼VMRdC6J{YCq2qA]7!}C̓?NKn 1c/':2) .2.\C~y}"s{vq*eb݊s`,9%GS8lc(>72s!e#ԁi=пec%zA{ Ԫ4~,UlɁ:SP~Xa}ENpH4 mu12ʤn RGZiΩ\ E9v2S4T6+h&rwdC)ty.сzڌe#y}ss-&z]h!_2.uc%!yM>0}^!M{yX綏lǪ@3C/WT/zK|O}zӟQ m{%L~~g~gt}o??O~v/:-7wTV|XzםwV_/^o|/mu0LDL6Ra)\l[}Bagh+t$ c:0r8E"ls]߸YÂs\džn@οqjtK%"% )~nu!yw׍@}puyg~n yoGߖ4`k+-uM6O_:ǀ{. 7#0kKk)XONG[8{k崷$:ˑ3RC#P;yh}N8p)n"cWo szkAlX>ҏ]~yWze ?C?T&?诿7VozSڤW?skl:4G_ =ԇO}Op6@U߫}UG+(?oykހwpJk 8e-3篁xrx6kyySl/b'AR39->LWd=ma݉K`Ǔw!+'ˮ>Gc=45.fFw 9h3Xu罷#kū6>+pÁwse:Kn| 2>K Uo>C6’μ._Gu?g|Q&)-.}];T_#GT#7:c+sTVu,VK=_v}$}MUx?fJog,hlo%c%eܮUɅ#:˦͒ As6CI[J`怖xgoqƛKP40q߹u_z>  {E76+D7u2v],3R]:/OHW~.,90wyo:Ͽ|uY}3&ƚ{!zDہ_6MFo׮կBj}Տ'x> aGoYFxK/,u8?7򘪔!*xؚzJCl))}>uO`Ĥ v_>_!~WYk.fJ'}i>,kTxYk| sūnԼ>J{fDzF@){ڳuW#omV]=捷>T}vkݏ5g]:fr*6k[Au֖Qe^ x:>=қ:Hn thȃ|ڗ>>@e0d%8*X2w5c-(P|^1iO-(/XyYÅf`P>O) O =-a>St*Z]a2wsw1KsrpA[ݦ&ݻ΢}UT/~ ;E0Mׁ*桾2m@34YQ4^.c1G9{i\\zȟ5Q;Kn7~ zoᛗm=:yFl⽛w_7Į&|M{nҗ]k_jKM!ݵkWsIe};]ۈ7:zի7n|52lA_s֟WJ.m+c>ӧe[ Kh:߲6kTLk4_΄pA8xWȺ _sC|.|Lg-7zt򇮩Ucw;?w + yp{n2kͱnӡt=:[VcEzXi>wyE^ J6L(e:H3ߍ1w/]@؇S;9(W}K)HcUϺû57ѬusqRc ڥGڢTb`kh蓎ﹲОWY 8W|c6ǻ7f7JWj}_@Mo?u>3/=]c9.;H478W.mjLŇp~=۶h@C<8s_/on+em_~3ѓJÇ׌.]O]e~ =xĸȠÕYE;9vSW1DbzۻƎƩӦ/>E[]oɪ~]aKJ*^_hW6'ʆbޡ^瘗!x\4нuE<ρn2_6_l^[Ư.y~r(vWE3Ψ8ȣjؾld/%ӕ~yyϞ,|H_xRu1T_pAuGVurnxz;zfSwvuqV_}YFYvPӞ#JhRxg@b0 &^ nx8&/q/ICst-g:k!KEFe1p%q Tn`tԛ2Kj=5iȌ7z뢥4{cN?jQWZá9ـc)}c̘yFڰ/ƨ#Z8dx7qv63=Wn~9E11rs.8w}[| sr< r~ٓx"ƮiCߐ ¯G{O[w8S.8aܤ`H'Ȏ۪{Љ~m 5tSn.sBzuG7mj)ZވTW\v!^׶%a3CqsŖy6aڷzO9; 9OtA7UO-76-D~@ucGp-6<4}%khr^[{&ϯaяyL?nSK=w~[Z:,57욈s;6}7S m]i vX]vya#_ky?6 |8cPץ8;Myg$EXeCa?`ү1n8`@R9O<Z8ÿA~Pk^_˷_.nruǕ_ݟ'4Ksm1]И>nn]ɕabg#Z3~K"mz.۲u 4V[cR> 8ܿ{Z}>TN׷=25]_ лOU|ďn?&ֻ%7 ŠT2I|vz=4Y2Ϟ[||9+t{#nm&/iBxE<ρ"Wמ},nuCO|ŧ?]\n ,LN 襓L܂Ź?0Bɧ`2.Wc"FvMԁohVŃ&6 q!uP/e )ǂoitrr!>*~AF;R: ^=n`gIk}FCeY~[jcv9SVuɨAtrl&Zb̹fMc:1q}Y/VNqVcԣ>vy_`Zgl>1⼉?O5]c9kreՇzφ}R9::#ڡG►!m}m$zy:U#~ן>8EF_Ϛ|H]|sYsꍻy>c;OZFT@mc~ݶwTCC.le-zY 4Sqr ~r,mLuN8#A>sa .e?eB%7γ:}hW=c?ꎶwDSgݽ}/h!:CBF>*FN$0 ?T^):)m㽻'<bֶ՟ܼ, %&^5eI}/xU_V{=.^^{|_U/eSzw_ʶS> ]8_[gZo=d:8-^Mx| 9>'dR:CiB!Xƕk}~0?N;{ȏ`k{ў5e7ıTDr4'm#nY+OcAi|:dN]2?8kr p|:Xr!9v%wA<`oK ѽG>j ;Eo1o _! #ׯ?Koƪz;.$O7?1=oʢ?c2Re-s寋#4ю٘''S`c~;%7pAVXy >X&>OH#l1FFI9rã0̢n[q?< oSݿ}2Kte_>_>G[1;^-l2"Yz=Ec|ht zo/QF| _kO:%N `ǘY bKMxG;5Ll?v/7K8YpzcOyY&9"^:Dm}j}2g,/8p@q9%6qkޥSsXl#mZ1AgH۠{Me[?->/(/_6#g 8lm[t?lMiqTSuW8rOG8nm?EMq*hX_N "؊8b c\e>fr0};7B_[C"*Xcj&7 ]E~j;)VTУr]|@ mt',} )\ZD~(}Xc1Gʡ#RVNpD7K)/xu>jx>wܔ#d YK^&y})ݕϽ]t.\~"D0]x G#7>SPU]X!:>|/<\=,RHps6>m/#e ^Yid%eR.ͣ^拪gd?M'Dv/$@IDAT8|\H覫ER[a#S:B;_<^q!Oq݈]IF| :\zӺa䇏_]xs}8hLJ<+_-yW_o'EXt.Yrz:A]"ޥ'tB nC+yKa(j0:Ynl-[2ڀA[J % [{62΢3 21C=6:ǬO$r:jcq}H+V§|C^Rf(Lt,Ҙc?] S4Cp^>!xpgH/ o\;P.wht:dęt<#cꛓNnQȇ%"fA?X1n7c+n'+4irq ӥ+Jj}+oz֊7)xE%E;c2n1##-#_3+w6ˋ޻!1;h5i\Ӧ{~E:9訴e0%c^NqVB?imq>nm+ͥCVAoWmpt C弚 !OuxAkW4?4VU]VEVgk+ۖwy'W(.}s@zI1E=%3N ]a4 <4^ooH ?$\fN ?cZn"k7RKiy'd(bp`Xm8ZŘZ-/#(s"uS?.s\nOK`W&7/ pws-,: ud޾0зw'/=zl,ˑ&;4>yP{5>P[I7߬Ât`Jc)wc=zF=ԛ~L @Jcq%mOۛu&#'}Yrnsh'T]V{DpC_PC4im!XﴕY-88ȃ935vX8CʮV'-bspJM@6anmٕbn쫜&kmu|1~MUsaΦ|ȕ/M(-7n:wE:Ɛo2NDMn_C&n|fIs~s(s6E.xd^tB+k]맹sa1.ruIoۥl V54x:5Ʌ =(-X2uel|ZȤx#>ϑހ[خ\|"og9LEa )1j XGL[m]_ʥtyyݮa9\td^urȋҁ]j9ϙxjAzﲉ} (xX0FW.Cx<1&<xRa>`D7<9yo~Os`mNiyXp 6U{,* \F= o{Pn}&:j(>.'&GK45Aa \m%8q)ay ARdߍҲ=~} /'qҏ9qpCkǵ7㮋8 vx_xa֚^sM׽ɍ<1MCjσ{ZIl/FsV~"en~|e}/r6ƧuK<4c/c΍p=g:OsF}ޙBO21oʯV:vM[|ͳK~CȜ=A;YEp9/g8߷.C p BC8.㪻Lѣ4 ) zT_)(ܠI7.E|d`-.2//n;oR,M\<8ǀ>'=Q77:mO) F_c^axGr\x֏<>yy.. ;0/73 _ĕZ?)C:̳+$4ջCBxF>t]JgvtJ,ɪ~zk^]'7p*K(Nj| 8 ,.A2*o Y0{O rLLiW^hT%x0 2)62789 yL7K#vvcTxXA޸eR .:c>4uwo|vkxoAzWQ˸(drk B/̓3eΣ>;O1uI_/!sҬO{#Z%>-)aX|΍0tV쩔0XY'4W݈ 1G28'cg.{KNǀMAtӁJɹׅz>qGeЬ[rimxU/}Jh8`,Fs sשHcd5:^t]8%'7Utf"Ǵ#"Ghc9C`Δ\E_xEZs`߾}[Ư;[Y/^D;׃mټdӇϺX63#h)vp[6=wlb:I.?wl# 廭7ܔbӟOÓ벾~W?D,;߱=- 񥺽̥,m"6HCvzN֛ )B&Y/8!v&q]q+Kќ*Ưޗ&S{<-}06SunNWEOnbkݕӫOq(^+:ܷ͔KtOcއ)dJEGqcBvnVu 8y7Jz\"^1y47z6M( >$}SyI}NѦJŸTZúSV?sY'4{v-sQj;8e?K%퀟G8=Q X]q5>i㙺y 7pCySK^|CDYёЕ kRyQ <0CQW8'MԯAy\z/zA& @Nguң(-P'MF;y')|AODZȳT=ȹ.Cg>x\\p+ {NJʩw裗G#>l@+4 2/E)Nh~O`cXYai̳^UKCr܀ 3tYt 1Λ7e=ΎErlGC΋]+YOI |6*xWH=$\ ʠcJ/YO9=1٠]O$4Ʃz1?I?r4TVjƩ;1wLtѱ^I ̘ oSa\շ ZK+gB7\Rv=6 RIgv)?Cwm2˃ǚKpIfkۖ ڧCC>ޡSK.oY)e(PL P녔p_]c_pe#GOD<^Y5zyRi cﲕ%ג+?<8]޻p~b.OL|6Ɛ]F\R~lj\NO-Z%[6%=CT[m4uWl%)V) ީm.x-ěOVǠݎ7Ɲ.93Jy"6N!ysR [B,[.:HA=^=i}UC{c0_8ͤ,MNrczۻ2š3U>D `зz Y@ϜU0.^4C87=88.xy}\۸dq٣,c!Cb{CPi8ڛ~hkG Wߴh/993۵:^TZ|N GӈSۿ+/[;b_9= >{色~I9 yiQV//?{ c('<gWxRe<]{=mfd".ޟL&ȫX_^Bi7.M=_q?r"οzN_銧x:`p2>%t~RO%|]N mሇ~V:7Ki{>!Mqd灓Ϛز;I)umt5\"~H펃 s$ 4qP6( Ak2 r22͚|0sҙfUϼܨf#/Zrz+XOoCg/Ѣ]c6*3m30H+6;YR4g9]:gMz[b|#T~,8lC> U mG^7(JP4Æ6̭b )6ܮG:f/Tbx9#:DlJ1(C6yŶnpakVOnd<=E6}ˤ92I Y'RS=ߐ sV!/9{"0aʫZǎ05ݏ[ލEP{sxХ1yz*c |:Z'ytCr0HD|JdO?E?4N-%z@ =>s24+]mcH-:ZiD_ Mt<޺qQ %4]}  H1OR"q_h~h$@W*Ӣ,e{==;2'޶CY[y)[}x:'ϛ?S=z &3s=x˺ҜGc +:ZzEKV:V%C]F67ڥt|\z ;Mc(3}WOe0̋Vgtؔ. X"fS%Xk0X`;ERf7R0Jc뛎R7*/tl.{00rpm f<!Cq SLzd/nUI4F1/:JZׇEY)oNmˎ&ܼ!qyg;wI$\i #XmL 0ezkFsc*?mvye8j;#t/uc2t^jsW*~:Sdj,*MHNԓ[d6=|fW=8Yߴm M)yFVS=E ɣ.ˆs‘XGD./\o5I׺qؒ8;^%x =.63oVx\ Ȃ;KӺ,=9BlKvKۻw[6;=^ O6K$.rnTC؋!]avƃÕ5)9,øxv@Rmp b9ͨma0t4?C戮 X;:uwx|c:mt;Uړx/CvD״vCԺJz#ku)TQNx6!% 7~d;т&B(;!{X-fns[8z6GP.3ȟ[PʧtԞ?/F Iט[ z S^ țzSaEe pV}_>e\>DF/HE|f&B+eo^7?tmTZNpЅ,IJ}J͛X[onSreiRˊ/GR)8\),)C!WtߵKU>5^$Dށ|ҩ|96m h%O'OC._Bד~DZuvot|䒹Gm%+?)C0*]8puqq)m7zX^WnɕO LicOytC cڮ6I7iF8G8nwӇ:7qWo)Xp?r qY⩺_n6I#g/U9ZIG/3Xhi; JnRtŵxFZn6䰑Cq|l)aP#\6|r~@5prK2rcO.#>Ȅʰ6rܴN`Y?1y>a X/ y^]ԗ9B1xy't^ oY:",:sOFS/x=Ac7sS=xHϽ/i>rz8u uE>C6!B[y=-gNH4GН?ןçtd^ <}m*-gV86s>J/sHNي|L)z胷ZLJ<. c+_p8HWNOK}.GI mEw9yMޘ5F7.twoSۻ./G)s/<S<>q6~ xO殘x_Ɂ!)y9կ(_yu-8b>tqޣ1w a!+xu<>ifQ iZ0O0phELmKx",,~b7>cO:HJ[㕶ݕaB@2rFmI Vy8=*n.m{7N=,nl‡/`Bmϼ:'ew|k^Kvs$~v@oGۊ5~ԎB'U_ۡ9zWҷ\T#}îڜf,)&WFt#>Ɉ6<GJ*K9 s *C?6˕[tk[rC\4t:\?L);f< 7mZ>^@G?`SjSIBVֽ(y_|njbW{`90-9sV8J@:~꫟S<ۖN=|.`R6“>trG1suEyOv.iwߺ;?= ɼKc΋T;qr aNk4rxB* |WZ1T?,}0}wyC'2xѿOz[Ș>RPiZ]ߺ+o>BՑK* W.׃<_n?N_zϛ>z}@=0't05z|RiReţMn˕N oۘ몃|n?|0Nѝ[b8,rw΂gПA s㗾u;We P{%_qxyy=.\^Df?;^"#Lh#z5(DBx@eՃ\ȯ|GpBT>'ͺ{ Fwi@"c;|I!#q<}*Os ?ύS}ұ!Q/]GI}HzWjϐ>G)[zGɬ?%-D_ Hn l d}wlFm}aiw?G8V[ GݞT::C~ woN\\ |+5!:v,gPR@1jvgj#E] O<r2)خBj.95}Ҡn BIo dK(!-%r2=EVd<-4g%yLeb-7],\>@RN)umdw-bYd-w?,̬iѰ_<E= ' Y'N^_*zm,jn3Y0>*2 ʁDzǻl q:uՐ~ JBq!>֢:GkuI`Og9$u1:_66Sdե?94>o!'mHE)SV~x f<ζS;eI} &I)̯%9Gr> 9Qa'5QTTH٨?L;3Ɓ:>"]rZJ  s;C}Ex8uh]O;JdGe|Yp`9^Peja[mb[]̗^[z#//!CC.gyO;~a,?VF>7>3\W:}%~c\{Ŗf+uH>/.CO\.̍ɵ"kt0w!Or2r?xpxZ8d]}:?xziOS1y䱇Ǭe:EHٸ$_f/OPw!v :13YL#+3%릥[]R{ _t pu&~xCKНtw=wX<όIcwl00g8=;'NoÀ,Ey NtN{ѓr,;v%q~} \n $9?(aP<8/B70~5eSea. J/'o_헜~؎HʇYnr|Ut?e\~Me?|qSq qPc>.9MIBc$,}~B:X!qti6\KMWb{Oچ#s\gg~RGJ}"*CqϺtNeGqx/x98DΟM.Qp*L) 1ZJ+A L 6<eK`qq* 99&UniPtjgp//WC''y]E^[#.ęi_\듮h.t:Nq0Nur^n%t(NS?>ȫ禫Մ?;uPwb,6$wb[l*#޶wƱe}_]ؚLͣ;hcg&[yZ0۶XF1m'z^d-xBϨp'Go{>ӢA$%+;?3)x'ʻeӶ-%;U}%6~!V4DO CSp6Ʌ>,𴈃yq;W:X߱ML8LW<է^椳Nh^/zqw>q^e3=c ^6R'*1v`z:]eh@|\212 PcV[J6pܧ ̚(};st,̧)\W |ONy{WզMnL#l4pxN)Y,rv9_S0 n3|c:"Z-[}3_٭MQ}h#. b?{oߢg=25\Y#Cػ-K}'Ftm~KwPB/0nuҝW2ϕCTQ6s_U2|E ]g|WWI^tk6G9tܤAm!Y1rMqE~S=Đ$Ri߻s/e%X/_fObYޑcbzbIcx<$=-9XBɇ~9/9\zG8mTMZ^b{3R~qaiBF oӗµmr*T\OJ6[]dy>5޾4Np!YQ[UO|utۜ Y*Oېh[/?ׯ)""Lݞy=!<~9\י5cIsxeixOwWAʧR >5ν^A1p{ naާ 89y[d^VJTnxOtdQ{uئ&q_CI<Q(lq@]eL,*g<_ILqFaLK @90tu}G!C[3b!ese+Iv0=,ȑ}7嘎Stݹ<4k'}Yub#rCb1ǝ?,Sp 9 !͗9 7$ ~I)_m"Zh~B䵔.||%DŽ9ï .yRW[~H+CĔ;]tq=E9ESNkt>S :<ʘߑrG~ \yn'G%8x{> m~ #gnNGFH!)+tFxήC^ M i;sm=ѮAmRԞtr)|K>2QRaj#iY-|C2џ+3cf:w4uQg "\r^*ЪPzD_Z/ LJJ>zSj)esLq .< WV::EY}y8 9}M#TpwyšY7s]`[a.ttC}}5| &ґ~%g7<*!%;cͦ!p8xBoӈ2*k)a_  ťg~48I{݈qloyWv;0A6#CpƙѱNd~?8i8azַx%;S=ȄԗsnzTeq?QᙷDyE#C;SǏƠ>FP:(/^.ּ $8p(u}Rǡa;D1:і] % 5y!G}^292_aMx_p`~p+;͖.C[mh';œV򠉃JCW ^{uQ6yӶcy9hQG i޼Ŧ@xE;} LKy֒7$CʖqRe]pǘ[ݎO>6O Af@IDATYoT=!>/p6SF=WzQ{[IgUyn[:H>sEXkHf"KW(A9/OـV܇\Q&"\|'4t!2B[#h++ 9C~T(:#4r-ه:J 7xS0JÎ{zˈ^փ耟Cqza/34~6&u*DgvڢZF\"FN pm!Fcmez^E0!@tIF}ߔy@&o(744nSqJG4weRk)Y8S䃇wJ~?AG#E{4pz) .7&VUzesTK|NL^eG[d"S> >Oٶm  Mx }]# rFw6iqa+΀b K0lFp)<Rq&TXi9e6j`qbKuH)$3CߧaS/E#01@hݔwsùz|gdడDT膝O(\tpPӣU~n5tڡw0IمS2.x5Ѕ^U/xڬ?{VG `|.as;q )ȈJۈ삗:~;5[0|R{ױ|JWgV,75Ok>Xpr硗n4rsI_7 !_c݋Mo>H}8>C6oא8xs2q"3>oHoD6W/wbw͟))@0'?t!tmͤ4ƟYs 4 Qjn>@VK'3!k0緗+s B/G'8\ "C>ӸTdяsùRbx$Z#o6 KⳔ-ahd7OSrvt)-Pl"m*ri]qې[nxa ,8PƁԜLI( i) }na>Wc{Ş7}ml/~઩o ҟ–oIr-H,!u gGUN?t@ȫC HuD$ o8s}8^_ W s%u4G eD^Igٝγ;NwNw֩ڻOI_9UjժUVZU{֬r4xI/A+2WV{ZϬ)F]K5zi W)Ш6|!Ϣ!8?}Q1rx9ip#{)Ugɾ}sr1uYiyuT::W}9A8)uuF ]I=Dž|+Cd43}8|5i6lb=[aϲ<#w)Y͵qZ9 psX2s{cG>r fIWg(cϹu(9SLC;<^5'+ mn Ο]sr~m_/'8}~.CF#0ֲȁ\u3K80s"I˜LX_T;AIaM!8KPΒzS=)ӈ _ AaÄ"l)/sՂh)YLQJM$iYLJpa  t-M,v,%81$^i Ɨ`RF? pKv6CYǀuc>CCHL.W9}9ʵ)֗m=s…A&y?A;dj_b]wr@oS#lgd^C16| E)1:Eg -aұ9X:\.r/#A;u*ӚNoKO .~*5HAsy/C<M)s|mp3$gy]ژWPq)S<6D%X)F4Ái-z9WaI3Go=o5'G?SHMȃ27 CT~y4"O1khJ?P)ސ4{Xvgcb;I>8ުtoǖT<~2ΜKhԡúJ|Q{> T9}  xymspEꗜ^+vbOysidPX?d̐X؆xisz&qzJo#c|L>bܥ hqp4k_J38P΁&cJ8B4gӔ6omu4CjCM \ا/-Yұ㒲bݶg{'{ʺb~_5r:!1c,:֗/bcGIS28{12Yڎܑ5֓݃"L34 ;U%r0C"1cRU 8Gؓ~Oo rE}Zu<=e:b_ S){TE֕X홳 ̫f[;^OcJ[㇦R=A 836\OG,ӯ(ch~ Av)wCWzWBtB>6G.)߷clK|蹦pPQhD 5 ]UghysycKb{grƘ5+=#w`1!qlc\ z䣯Gi{.yŌ15s c ̋=Cd^*cMYԇ8V =yon. '|RrϹޙ0߱ot}"}@w]%վTxv:m{e6#~o>qz~40O;ՠTL6<<A_؞y৞Fg36'vTDyyM~mO OhǾv>S_@e4&e؇T27/֧Wi8|HmtJ]&xjݼ1bWߕOs=Riܤ+i8'I_p 쓇1-3v܌'I]_'2ڧX?qx]d=769]̂Es#aT89}i-g|9:r0C˧fMq6;"GӠ%Ε:[JQQ7ڥ 雒u/nx&&JvFgrmRZC+86N[J W9nQ9\' ]ܲvr/Zzgdq:qCbJcW!몋}Ox>_kP?t9r$GJ4?VU@R܂)xƁ&-ӱɾz=q?i ϥq]P#y]NS~ `y]y|=恧>4͇,Z'{4un lZ|51rLC^a0ghn>a_l;zDSݔmCmuO'GyL9h,<}؛̑6p.dqGea ]#:Oe.88}wUq*0i/wnV~.u*; xqa}f)[pBl:^Dߤlxzq(-96=Ƕ4(otDQ1< ost{hwx~LDsK$ό1k$ߓ.<ѳy k:cNkb>3Kg}"ݮgƘ2Ϲ(?/678xLϓ_6Yo#MEm>Y#XzFwia^`=OMrs^(Xu雎:5np©n6}EMұ>u8T~.\Snbe ߹};HqB/9|LcSsn?'ȡG] 8E N:U Y_~H9 ;<eB̜dSs7lP"cP]ϐIW,|14yRc_7˛,? +n{pE3ƕ[s"t\"¯"a(j6lD[2Po`ЧPx3^2wx9J7 .JqIyR Ƞ}H[:l<>i Zr۹̘:C/<ǘ#?>}Np="|34vbFXR. ywy;#sҴ:)wW8Ϻ:;nEgׅk\0at#|͡aˍ)x6GRp˭c>}жA_)v>`m+qn_xθ!/㼁N;bdul;DcU$V~{`4s0 z:|N`-|йNJS6Vׅ_)k':1}Ϊ^[\% os#-uߴ>'n+8{em*s::ǫt;TLoڒyy*AGmEӎS_c:ko{kl\c/qG 9e#Yx$?9gK-I?Շ^pڣ|80@7Qb.}7(2=:o9@:`XNJ~GG U? :i;G2}6 UO.ƶ/>YyuG`TZ|Ij9J~!gzsh>`1G3}v\˪/6m^u9V]x)4Я`w;P/(w}˜cuF0crJ}>23dǘFX}aJiyM8mg(AerY2xxH}`sb>)~Y7YwGrl3۠`~'v#C4D>E3z(q=no ˴IklEt0RiƲ>-ﺹ}6Puɑxz gb9 LCz*?ƥ] x2vGw F9DRgr~ɜ95Yہ)YkRN۝݀t/n;/uv-շN iȹGCV'THoIS{{ۉz xY⚙낾>S*c;$͜Ə{>dj>|$4vּ:B *%/+pֱo EKYn@T N/0-b&%8&|1T f)7>{AJmFء1_H)xɭbKi# h^-I9N9解O7xR}y.REZJ#3ɒ|POygdNiԋ{rjc5F30Re<9σ:|:m,}\U}K~rό;F{ NN5u|uV1C\n4?zЍ)ljrkQ~:c~JKƴ/ӆ6!.}usɇzۢNWΈ)_ T[3^ZS䡯G*;^WiUh֭ >)d,s3RkStQ'+.G׋)|G{?N9_y`SxD=Ŭ9ם<ɔc0{ZOix`|}1e;ݞvL>OSGՓPJr?_1JuG$t܈h6|2/7䞱(H:2UiXk8kp8Oö`)kP[]ʨsf}c N2t0V.W ?eǩ9 Mqº 2Q\sm2Gڔ{$zp?80@7Rs O|7vUt.H׎)_5v65:Azmbze>DS\ ٵn=rض-nPp(= g9ك|(Gb20}Ӯ.cy%i1C8Hm#7x?px+__EycŎ-Ad qTyA;v|%iߏX % \@ϱ 綘y5mڀF3C)VR!y[^ό)-;3/~:G==Gu&|+}Q?,`u񵗖]2E=.[F +-䲥ҠA7ndqx۞K E~kmK]A_u{-b6p_7g g'5WKGG85޾EЦ_|M}>FO88RNuz}Kϫrb6 Gqc+vXwvpʻ:R܎T:#l/ ] .ikY3'$tr͎q R$цFʦbiF8;6jPO7BB6'9eRŏoMn6Ϋ_*6o/:w[ ٝ~x&/VyIFO4bŗ~(Rb$= I_"f xhOn^n;[;4!7Fic9t.N[0w)\s 25pcL{^.-~yܰ?Om+9)G]ɡg Wq h\3u9:)D1PܵCP g<`y(1>iE R$FGl,<yɋtz5u6[͂e TzxdHvr}}s&.de~? |M 7:\*[ݖ BO^FD&(GU ȣϩC9g-vd](FV#T&~v$X/HSdG >֔)8( oaM zy'cknMwW.kH[}\(\>LЇWmA >a?&`7]0Xhc}<Xi:j[A)]b5H:47ǜ4邛80@9Ct[{t_5r|)ICNn~ -6f,)C([o >0XFbO9LiՑl2fi"[sio-IK"~%#Ll'}ihIئ!a4b9s->a&S3eg{ӑwq"C: -{N-Or.z~$G}Ab>R<`/<#g]#zln,şvr.2o#[!>'X\/z]eXy.U.eES.ȽNKW}T-#Ha%?ގ IVQ(w}i@T2Cl;7mjm{\Z8,`_76UYCmD2`Ek2L ȉ.N.OJ%`wRW[c}?D4f~s^f6Om}~Fy8`ú#Y[CSe=} Q~X?yqsML*',Sp~sXdZ0O{0^Ov^ԚqE7QڑD\G]wN4[ `|O#N4ijb^6GGk/#h~𳛂- ‡B'9rKf:&@g\97nB]5XyRc~2 )JocQ:IM4y)%O>xuՕ oc񙵭6uRϑ#:U|]F r!i2Π-H؏'x"{"?^$nl_\^q Ni qj' uSexƁ936֐!Th_k4#6I9pN3h<ƢJMx!s|atJs$= 7{v"Xgp*Y!{~NlHۆu8_+7;8%AyV{n"$vZ ''n4m u|(׎#ɻ xss;hN/>7o* }2?_Ue0ί~dRucۥW"Qg?mXͿ~9A䰞l4t9;}P"5#M[]ռXO+U֏EsJSqJ#x}GϹ(Of]t~oT Yk{[uKj~σC@FОYcdk'`cnxX{NZ Osug#̜g0m@w`QH GD8F/t ^3* R(FѪ?H,xXQJs(_ nTKh`U6c+I/6Pq~'f#E1fby3FL|/^l Oa ,=oҴ{+T==C":'q|C?r e1'ISP7 /'Z=݇v{+2 ^$v#|2R !$ucN3_Lkb}t8@l X>suE[?e+0nqe#,t;~\Icǁ1pǶrϴMrpAu !%1뜜epE{Q7:\i8pA.Ux\ɘ4`#Je\5Gv i}E~,5S16B<`3St .xm>ibrt&<lj8뒦KdrbPYs[?&xڠ.@l :-q^C\r4~.қ7lup'3s9!?;yTW;e=6vm2<+}^*u^`SxE{r7Qc~T43u!|q\ǰ74uؽmۆ'W&YD'ѧmSey11 19_j *i=G,)+>]h}bߔsRE]{MMC/{1x^n/Jϣp-^Ǿ *,Ik|OF{e 1_} $2\xp ґj ش;^zxF_NOӠk^>~-O`LYrAM9Ƚk=G3jz~| vu3luզ.PѸa/WZrX>ǚ'|y8R8~6Nq@ȇW#>dLc^;#] aCG.n[ UC`zT.G乷ѕF xn,73\cn^{xR9^!} gڕSB1eخ'<;L.Dʿ#st@]~M Z?OnT9k8͑oș{92'1҅qZ>2CI[srfnCVss_xAb8Wo ZphyrKyFKbqXg)x|=!/Gމ_\]Ǿjg,Wt/zf(_f( qR3##3pG{ԗ>zْ:}r_y 8ĴSqn>:,=ÚȘ{DWucIC [9;4\􍷧>k-yV JT^I`l[K8 )̾/=,'x#v_H]==׻W58춦集f0'OdXC7l{fݖ}pP&g ZuӇDk>]}.+ ϊqv4K!MR<N6ߪ璂48B>CiO]77Yy]8 +} uirjIf\;1c__`G{ |幍eק=t/^ 趾?/6|hkiU Cizh<:C̙!usu&Oy 9W]vb[zި.^昋TC?8J7Os j6W_|9kKq=hXSP>➱ *mZ{dɼ/cg{͆c8tjkז6X=.w^wnK"LQn-Mͯ}%E`Օ3Wbȇ? i̜|yLbp i(Pl%p߶ڠ+`x,mv__RPa=͋q'J~cwy"8N.oĴ:LX|@U(6 q?ϋi^fK板ć~]y3ȏ8}>j{PCQ+;nl@MDTn;$_O%k׍vpJ|h[0}d!w;L$:v;tMkMfƖ8̂At'gb֭(q]F )pb[B'tSyy* _vDV`)l(-+ vV69^QMtU,*tIj?ԇ8LѓRp<׫ {=d R>@3xC$?vmmA?f>{OlL2Oԇh vߩef ڙC=K}SK7L*9ZR'pٽ_GÆmBR}N94?k{4nlKӥR޷],9u|c0tŤt:hO{Rt{՗syWu"MyWts3(;YWԃ֔n$v\T//ӥx$}2>Cz +kb𯛻]tP[ =#X}{sBMB:btr<؄y?M4oxk?צ4W|gz=,ݮ7n[(pMsWʶve3'ǺX10dŠ_6Q͕ye-8 搎]}0(F/P PAQ YyCAtіz' lOs;Hemi^::LQLИ=: cp[v\ԓg%d鋾I 62'#Y`%4xvs2Iyi,3|x, q ?!A sN ?ܪ0ixeJhQ5n-@!L7dj!4bGǾӥpart/xpm;8Ԑ N;dzk%fL8EGJ몍lw^ӌ{֚{Ԝug:x gS<GFe[xťיgq`ip6-;';퇦Wi7cCpNjs]xe!{P\/~'6S43걖jme=&OŔHא}psɊwn=m <#c?ASW)>K7&OK'u}:mi`<;\c,'H4)LplqKsKsѭ};\55W>$9I՝=p7dU]G]T bM#E~Gá}IqKSmy.g muʘC{|-Kc1JqE8Ni $]K_7=m~)Of; <_{ PruC휧q[i1ãwiiczy/1Z}iD=<߲K6C6(QD9<1P 4+~rt縭ϯ>=j )ԋȡ4cw4I3G ~Ghf&orU1j_ F:U䠫=Q7a>laTȣ˒{I{~Y7EruSN]uyN꣧ .ӷ/n4Th sY>W>]&*G˱/┮%-U[i~1n%,głIᰞfF/XhJX><3.?Ӷ0_@IDAT/'SUql[{ RIx2_8E2φ2q/<Xq|QxE'd뒦o<Qm4^狏źH* Ё/G&7N7vj^23ojvmjXc~ϵ:Fzh Q/N|s.-{ҕz2.Gǯ:맞}gh#څWry):Rrm =dI3xNgC^KǙ_h:sAA ڈkP3{[u)fD}< %/R׾aDjoH>òmϟVqCqtLKIt&Pʋh7T6cNFΧ!7rOKʲﴮNQҋw )'BarV$GS Z4N{oG.sg}G%KI{/f('ڱyGNKߣvT! Vs%/g)iSmAK |̻sY~vzMS{oя^Xu6/zK{s}ݷ:`kVO{u<:ыޭ[V_:W/|Q~#kkz*youG>ۉohvGrxo8[ l<˹ 5v☆Ab#7A[N \M TKMtSӪi^xzaҝ؜NSIE*#t"-#g<^pMC./0 /4eg|^rg}ڕ\K\><=_@?L}e%3:H'90XFaVK5/Ee4eO*Y1Ra_.&!#.95mV]_COmv pl̩CbC';ҥFCSN늗yPH80H&EsQ)`Ǝ},XC)S!?f2mz$]]`7?[/4O\[OIont2m"OMFY.~H{1z zD#cu/F/6_+jD3r)Du,2^,ked^|~tC~vT;5}o9Pm0^.zѿ?#6=O#gq^#qKch6䜞B7%&hyыp|F6k;XAs =ДKoȉ@/PN8F/dt+V>O}^ƒGo9J.(6|V~[Q_X~B.tR4F}LT-zH8Spy3i2*0rղ?c}Ә%e5dtpڊjY<yJ7:<_GE,~tp@gO}S+V4rGϫ;w~ǫӟtzCm~W_/uW]VW}.I^zHJvN\t6ы9`뫈'Ӟ^>U:a?Ow"GlCٲ})ar Ϟ*9)_)zknyFcO>k9WtyV8=k 9E ^ly6TStyt]{~t̅h6]9r9u&}!x%ȱMxxt>LbO;>K[UdlBfHƍ#/I?Gz۟US5FÁ˥sFUϝZP8:;̕gG5b0O%Yzyp1 >8opx(Fv9J\ Y;Rk>6/qݽJD"<\kG 1,=㌣64y2}]5-84Eы! |}T3|2de>h﹵|lOdH?D#t2@t{ϫ._ JgW;c{5oV_[9[N;:w!;4Βq`qxsm1N=|5h2dX}dlwA#VJ@qc:)9ɟghvslۢ/R1ً;wYMU>B&AJtC/t=}Lb9]]Qa";ԺouBJv#jKDŽ=nJ?Q$˓agh1g$y0CWi==c):FvOOVꇪ>xoxӛhAY.cٲeUG峽s !~EG9 C>k1C=ژҝ?멯/{ey}=Cփ)89=x?M Ym"7mK~tgmi+3GLOwًܰY]tQ'm ?v9N.*8Rn;lQ`o~,Ͼ2:e\+pA}7þmf|S/3o<$AțQ&4*Em)0_"M)'!Ր7G WU1tLn K3 fU}iN%4>hw܂(x92t(Isg7g?_pEEy*F)l# mkM [s1tkxn9[񃵳w^t7z__DX9v ޱcGuM7Oғ|p="${)sl1vbiѿvnRpZMrmLgϻֆHO3kx\ۓ mmoi[ ֮nmnn ?ZږdۂSKɕ3`G9jen|k'O}f 6|n/Oso@J:]e.]/x8=.C*zyx« Ϙds&[gOgȡO;OqìԤ՟q ǁh ҃}m%|hWJ/e=dw,"?I1v]TŸK v8_SP+|S+^GM]u -{͕/I

; V}ɄrE3?¡ VG8#bDprR~4 <sqt!28yYiJ7XzwɧOy4!7cL8e"zJn"5o$O);8gOȲp+<?rA4bw=4z[n:><\=ɪPV:OuKƩk|'sRcl#xVҎhLrE yHB4te>hXChK4~Q<.Vs/ӛK7}n`Ks%A`TY\']4 O%2X ^Ŝz4 JHQ>DNJ8O&[=oepe%1G3ꈾ1{C| {LEŒ38h+ wz&Z'-X']'b9wZ; _bW46{+ )@o]6O=~rh|衇JYw^%Z3,t N9Fppך%Y_[`maϸ6I?0%kIC9uOb10)' ^sTTĈO?㩹Pmf3e1֋[9+ ے_|FtöBW XKhχc^w9O6]kI_yV`S .8wNNry wة3||δx»֣2HiJ>eR%^pb?"~L;OnzKuT\ ̿;ﭞg??rZ{W_U/kҡUW^Y/[ev=3^e꿼 ^ 2,Tx˺0|8=9С)1x#U2vU$oZ>D갺yҗ.?m.־w5ob5K}X[K1nuOSӎhB2)[rxs'־ni9U핏\9]_1,HܸPq~rXxDZpؾitюJG*!L!TYG'19EOůvh=LjnhOuRr .bXV歝Nz٠񔤱q2Wk@/1,WR G+%t`v>~Dz}0©ओOV9__y௿J˱W'M6̓)}xs[]֜NJwեf?j͚5' O8ҥ~o>OWj)#tA/ ag/4\>lkQH8gˠ`X?Y'`!vC9yKWc ;j6[Cz-qx}\[n9EL~*ãe-<VLU7.ėOb\#Jnt}oc\?'T!9-3fVx&^hǺfM9?/`}/\OhN7iZe}Dx yasuXߺCFY7 Vk; 3#Rm@/=?yȭŴ2>uAj7gOL=[WPg;KYS[Xu Rki:{J/p\uz7WhV/8wr;/.xٯzжO³ʵWW(9vzwU:w:sb6t:N%w1+{h籈>,Ŭϻg-809`¨ՖT{3P}K bŊzOT>c5O|bVWߤ&kͭ6 A>/֦ v(T愃~tZq3/pR8p' q.IuƑāsq GI/q8r4 /c)!tΦrtR`K0:dY0.^$zTG_|Aqؾi-'- ^N- }yhC/%;tK\w/S*mqiuӆ =.uŚs̚<<{o8,g `ͷx\#k6\o*YæHߖN}b Lc ۣ Wqm/2m6m$ )XK_8<׵[ @ͳTp| \yo]-h#*|Sa5o`9%Ig fѫ Jl./r@<縉=Jث 6sΞ*R$h|v%Yo(F/%{6^V &b[=vl Snbbq1ʋpJ8`GZX?H+/Zg:=&_oQp/ɛ}l(C=/Wz}Bx~?# MTg{vv}ı\2>$:P>z\Ϫyѷ9~X1K|_~=u۾~wSy>>Tsq6jݭٛml~P9xo;zwʑm]YэVPAm۫->X^vT~,~j\5].)Gjk^|dUmCV캵WnxƳbu{q6ݢ~G5ZvnќX7=>;G|j!-gV-[UwAy#@ʜica+X|yS]/$#=*Sgots_9'94=LWpx~'6w`3Cɖq Tu w=dZߺm~)"5!WgT|a<xx$8I_yTa>1‡T;0IEeg@ϔcԃQZ Yn Sί/Kɢ镵~d{VKZtI4)" +W .׎eՊs z4gdu!2:rو&Q?M7g:1?ߛ旽K<ZCM%6{k/2I{1$zi_s>'>{Fm珖y6ٴ=#8=yh}UgaUw]{_59uG-GfK}w{u76Hw]dY{/gHq`7˛q`ol:Y];z˪~9r-G ڪs!"W_MTJ ϡ9\bBu:Šoи];w᧝A>ScR m }zV2r2o{c4iSmM~hlokU0wƶq"^Q ǀQI|\]ћx,}|D.@3O٤M7nhCڗrxC5~}vݸ[̭.^_}-K9]F'Ht _y5/v \' QY5=쾜^tɡ`M[bvFvi G'4 :-5?}-ukU-k*eJ3ibyis>sW_j+ r">|C\n\|b݇3L?pdCՃK<8Ҿ;~7ﳶrҡp}~>$Ga>s 79{]yYɌ38038k/??[4șzq:r>3<կ~MsKn8OoP_in3iÿiӦJ1ر^f0 Jŷ~{u Y>757t<)O\ S~Vok׎K7 Q3 N޷: wϺMÎ+&Nvduܭ7nXOG힉NNh(۪o{7U'>qշ/o cި7U-{1zӭղ{U?jɣՏq8z OP۪& !k?=:*<'a{YTvUR.]!Tu=iuڸ @pW3fn+kmպ񐊶TyTw9wYwՓ5?u>[߸ˮ~tA%]j;k]sLuWnj@䛠6֪1}TMcvշzIm=ёR+xÛ_naͦM>S%FIqm,5B+kg$ ?ET[[jSlղor (+CA%t,b<:kܴmSC˿yUC)оKPzu j' MkF}OM؄>rW^qCϬAn ;c8cÝu_n]>Q.6l&_R { Ym6!PqL8KF%1#55o֛6^mY.G8SzK_/̭ɹr4>Y4/~ϩwݙ<yUy0}A-To֯/zډOna}uڼ8Ǻ=`fp`>;oܰ`^.yb{%O ^36zvz&Kudft-% ']9ZJ}2bq`|Y,zfxgX͓8*4moEݵ[&xOVrUܮyx ᆪ\g%&<wtnJ>CWjO8~./ oIV#s8jU}xᑚ/r$ٻ8)/9gDTQA TPbF̞)Y #($uE 3Zٝ˿-ӹ~*\r*T`_;nH/Z"sC\GVuo!-776)kxvS)/2fЗ,|bslBF@WڲyWг[̯͆۾,/y SѬi}͟wZ}&u;tیl8,DZ'ߧ sϜwWʰst6u^VN<0eȏݨuZ)I gSf/eypCֱ[вSGk߿`?ǿwB:<)s~f<i{Nw1mGT|>h֙Fn~= .c%6q<~I MDZ?77|nqTL-Jf3e5Kay l;:%}.rY(!nWo@IDATG 9S"_[e@(I"wN@)6#hBٸ30Y G)e@}{۶mXsq^oIݱփ T^"Mp= @a 2矽cG+.!nE;|hZiРlQyA*\JQӿDAzv4A.Ɨ?p7qcr9=cJx*f-ɻj^5%3Qws}tl#uh/l9)7UI*W1/ i8 @hm;Tӣ̍OaE^꼇bnq7:` .x}vH.G` ] 6 xĀI^c\pᖉwq~p8{.h-msm斏5AUˇ;>Y'msۮuXwa6rDA6Yts]`zME٧~VAF}X?}w%A_qwK9^}O]RsUAtvc<8mT2~hy_2s iͰ_2pv,wkR#ݸnI^7ˋwH]ۯuy&ܾ:Mc?~1@9hZz Y>[Aĭ?`;}GĺyK;i3$GkqklG h`#VS# #v=[2rF.@(Zh(b]@@ D|eϞ^}GLĊmY J`:Y3;I2ev}ϫxGiܸ=mڴת][j`XԨʛ /Md5 QÆ'1[IfϞmg-[VͿXXE K>\܍z*uf^kz,zzV8{k0Xϰ */蔨$ƴ1k^7b^LOV#pse4pYWs ].+ ߧ3ZaoސА3tX_ֹmXɿxV ~¶q3e+Q'j{n;̍`K.e~R•hߗ2ypH/u`KrkyNx>}/0?zvTV}O[ЇґwZ4P}'k+ZŻt{eVIoh 7ebL>T}&znvĺ;t~No/ pY=;3Ҿ v\[gZ¶ 8 ^|\,Y,[=vː֩Ͼv\e|D0iDqE2o\H ӣ}>iqT\4z+n3 JU\~˗7l!sǣXE@@@% b k*joF*Oo*s=/;[߭T27پjk[ٛd '͟7O~=LmXӠ?0in]:OӲe-4m5Ùg`HpƙRwg LaC mC7nxG."m@`p_nUpmWnؽq?z3Mo@hxݱƶf`Qnpjkl}#NQ}z]Z<î@]׿M^8Jxu4)jAQvi:izpxπܗﳨAkv^-Ʋpky;bٌbըqJ}%p` τquג?ԕDo* ~\wpHt/^S=?=G#\7}87W&T._woĺoMQOЪ\>}w9u;,>Hx㮽݄(_\L nuXk{@e=.)4X[,SO2i&?~/mڶ逿N/tͶ5Լy 냯XWꃃyd@@@@J@^TkwWݺ{[\Boo^yَ73-_4ܫho9];[_N>x~Q\Fhu&\?Dgd -FE.@ٿ pOKp@ ctc,p7x_&n""uiZ{\]@[?8GzUKm9ҟ6AZ52)e&YRx +(x?;z!NY ϢyϏe_ʉ[b*{ERWEݩA~ykQ?]B"׊os;e2u zh[@՞$Pa`nEyueX73>G@[pMC3 ^Bc CM~"έᶥ&mmL&% qYhsn{rKr夔YVֹ9p/X>7-q䑶>}{ #oeOcF.;aW2-$:     Pj`|kE_a#) 4gNh[o_xk]0;{,KcnXI[:';)77W4؟\WvڂeZt!nZD1ܶm }]y~nלlS!=Z65kz^-o}D]G5s](2{; b%$M&Y'2F]kG^k Z^ӕ[PPXaF3_سdrnmϋp])p0+8϶uKf.zkoB\r6&mAQl[$A ׮J]k/?`SطæIֈgc(l=i\Jd9<9~w2zЗ0~Հ )jKd7<nZ#77jjk,?dee2    %N )-wqA}rfN&8[xu:M2eJ@ۊ2}4;wNL[#..R?o|5@yLY:0R mXEg$<"Ǚm*jגM}+`gT|QG*.O6}iT3ZXuѼ&51&`h֥D%5\ĉO@7k앨v=.Jb1`TdcMsyNӖ)d}g;kB[TK/I%|ԀYwD<}Dt_ZӚ?V>\ۉ֖+ת$rZmk#\ lQ^!$kIL@X~pӢ5f PZo~%9?WAI90?i`{y@".7wC2-Zh6-֧`9eY|Q158}ݾn:7h_]0z6ZF0H5dź>8$Ì     @^Zz{|g.֮]+Mp,IփLjߚ.R4x1.#J\w L%-pLhpf |MMd@Gµ /~G v*&PQSܾvWץr˥~Gk ~O'#(\{Ǣڪq@zʋx"n[U5H5Qn55 ~w.x߲q"-,cz|ck LO$FHWp5/w1c_AI?IڼRRnO~2>80    [ ".xLOE *oD[оt^I8*,:_{je4I24w r_3[Ϗwzz`CkrφKzmdqJ]V嘃Sԗ}%ۻ$mߵb@z}hΔ䷬?3ݐ2>I>񃿗 $y/lЇLfƑq       8 SR,[@$T|#{wEddNyl[lˣ0ZFaЮ%!:vIxSo"UM h`ԑ>SqTǹ:EVvyLF@@@@@@HA鳷{.W6-(k^K.I]+qe-ٻZLnpuhlZl3NUlq25mĀI!.=C x"č-<(D`6  2ei! +    &Ο?_W 6n͛7hҳ{|abYg}MV1ײ]&KnW?.XkM5+毲$g3slU-$G'Pv)~1  P<fϚ%jՖEƔcba@@@@HAI.lzEv|jW$" hULWh0A_Ƶa-eȚMkKԧM`d`[5=Aơ  dmۤwuDJ     $f3O3=ɤ#ZpWk/ٸfc,R\[`ӑ&ƒU`m|yd >J R%ۏEH       2yDB5cKBe(Y3+Y"7        D!P:eX- -f2         `"c          n&@n]@@@@@@@@@@"3          n&@n]@@@@@@@@@@"3          n&@n]@@@@@@@@@@"3          n&@n]@@@@@@@@@@"3          n&@n]@@@@@@@@@@"3          n&@n]@@@@@@@@@@"3          n&@n]@@@@@@@@@@"3          n&Pv7oҲor 'ŋqc6          @QRD\^=iwKVVV}'H"}8aD\h{đhvvL?͊ZL9ӥLƛΙ+sα뼇 uԑAqDEf          d@J;w9K[Fn*u&ׯ:vT\9:;v쐜M2qĈ˸=z7۷o,ٳga_o&Թ7o1hxٲy[n1     ǝp46͘6M~?`@@@@H@҃GN:P7fcRҥK׮!n{ vkG5XTRu7;L'!    D'Цm[g_~GozMsrseqx{%}k#"$t@@@@(iI "._& ,&K쫕+v4h Պ3:6X[(n֬]eH=ꋶ)iÏ 6ȵ_/}}t.[hѵBx_]V\)ׯƍwy^mj?#z@@@@@LHZq"3cV$َoܸQƍ[h.oyC]˯^hpđG~ )'ϧ~[B =5^4h x]_[Go_Yv\嬳eժtR;@@@@@t h+1.wGdOtg.v"mN>wuZ{gޢuϾ_9dg+-Z={:끏Nt!     @Eqr<իTn]i޼t=\u-[wp_˗/'&^|>= VN֏?ֈ@_,{ϛګ;%=.ҥKݺۊfoy$Eʕ+Kjי3lx '(=tAe     PrVժ^|K^yU;@\_7{~Xnj{s۶nsW7^M:yt)qDHsgɓ&Q53k&3{ˤK3[(Y!!    5xk?AzNflK5rnN[ ۚDu6v3K/]5mz`]v_0i+MqŊl2ٶrx̙e& w`-&>cۢ!|г>쳐 3q( sX P>J[L @9I"(PN&JĿdT4.i?իWOቦ1U֭[J*ilټ>T߰뺞4إ}|wfN3;F q&Qwt PBlvUl(' @yI:,vbΪCz e֖z5pVvWN"~:\˖-zҩsZ6̏6mڸ1\eԭ'5M7vcv tv*T͛/l[Xdx/~(Mg@@@@(If{[矒2fhdz^+>Sk)8^Acƌx<6qf`zie;:#    @ ڰ)/pW+[>vV:۶m'RR%9D5dgoI˔)c󯣭kv:udZ+.oW(Z!?{ǚq^$m?[{=rnҥ     %A`ĉ7$gwjgٱ[a]í3 ϗ ZW5eo$5Lc39s       =۶e}븙g6 @KzƍL6-_oA;U6TbEQATtiidܨa|-gw$g϶Ōu/0     PY`g)Su'mڴ)pa3]X&"    @ W{LhW^%ݻ_zꅌGga'i_KgG=<<"Vn߾]>è jn]seϞvXlY]\勺x4m0oذ y-4[      @ lZTתUsu˗j}}.Rr=绞tzA=qNS鮸z`3] z    D+QA'\t\yr7RżY=<mγvF࿍7=w Lma /x'Ow_ȌvkY8/y M0AfϞXchZb ~[HvYpFWvD58ٿw    ;-[1!sرc^V^p47 6h͑-{&^*oTREXm(g\ݭwU=ڂz(}g^=ӅP2    Q%RuiΜ@[mMwĉfj+_t;\YbEȼFl-:';kӠbrȶKeNنkP CrLƌ-zLu;55=u >t.2-k99<@@@@@@sϓ^.Gy=ꖭZɐߑgVAÚ4,O?W_:i⤘I]pZ5ewEڴmk/3]>w'`g     Ii(/c3ԐA*UJ3;v22<ܷqciRjU5_vUlK9& Y[ mǟjZȕ ?Vhg*m*jגAV,[X:(TT6'    +Pyg]h (ĚzUch7}wu ֻIŊ>wm- H=޹ϛ'7\wڴ3Q}*O<{ΰU_.6lq0u]:o_4,_Oܵ װh~yQۘ=ӭ[V.qILӺ-    ?DͤksCx=!iũTN4ñrJ)&Eڷ~,    d=m o˖-=i٬ӧ"Er-{ tyy`|,mڈ5&M6UIk5AMj}*SݺukcK_htSLO9k>{ۧn6n=wE'    D+Q-G{%i9D e%){@@@@ % :tD\y-ZڵE ƺk˖-Nݺ6xyOd hKî%Eʵ={홮U+Ӣ^3钁6@@@@@@Yo@@@@@3fEJӦM4+%Mv?gc'=     I&A۽ҥy;bcQ@@@@@@@@@@8h8ND6|)_lڸQ6oޜͲ@@@@@@@@@@" D&53=djv^@@@@@"PlCtq    $I $Y@@@@@)0{,U,Z0 3]L\,    I(OF;>Jّ7L3SCҎ2t=     @*WjbŊoۿzF@@@@0Dΰq8           dI>GXqrG.ׯ['Æ {o9hDL9_ɔI"SЌw-[J*Ubkm۶)SFn_Ҡ~}ٲulٲEF!Y?qf @ˉ~޻{Ͼ^9kywdE9HÌ$RN d B9 'Ǵd hBmܸ͝3WΝoI>&$Q r9pmU/[Ԭ,;vL\Gڬy 9Jʲ[zK) M۶_dnrW#aYU xC2:R|yS3"bsN)IHwy1'|+/s̵o]`)/03.'sKes]R>92"B9 $  Rz9ܾ}L0֭EO@5EKysC7=('~ "zXb$"*ұC{{7~޹^{ҽ59օV`]MӦM3O^&ڲ/{^5-m.@ K3ϰhP˃K3:HеKgvjnkeK]77ccywv@.'ymʧ&}2ٝ;ydz wy8 ^-r)DR JtH)'w?Mb9@ 5sKuKo.F7o]UOS B e)3M*zm-s=sM)Z^~N?Xr~ BH(/!$Q $\}pQN©0-P^lYGO\|IRKIVzo3@PN~ۦK}Oj^tGa>MI9fdB9 LH@&-7yZ".Hy%E xC굯YCt`|۠N8a]F13Xo)u/mÝ_8)P^F>J>|'}0ٴ嬳mKxS#,ʋ5PN|lkxz^YkzM'PNjB 7)z}֯3eq%h]țo!3g5f綫k֬)͚5oGne;sLMȑܣwؼL IӦ}ؘ.=w|GRvm۳c9#[(ƴD[|dd$}R}i8\Z"o6e 1n y]pl7AZtuKOZW.{/dӖåUVyvO0]KMhLÇ8'!PTL('H)g|('HdL('.uIKC(pK0QN"'[ #tV4r$R rwEl "]qvqЇiӊ0Ѣ(/AƓ- 屣]Gџ~In%I8B Sʋo͛Da&d S^H)Ҽp- ״7yĐNhViݺ01tܹs$l=+/(WP`)'03]g6o O_TnD/~f#oyOk@J謩 $'H$K DnٲeQN0!.'>+W^<>QӔQSN»05=.'K.*X(Enɻ>ٶmH'ժV$.ȗ3µh|LLH@*ʇf7HSa{&hX,paPUuhRzzoL@ ./w.w{jY\8 ^3A D[l9恔$T1,$, ,AM$K4J,jXˋw2p1u.jZdqF~6dԦm[,2_$lٻqiզ WIPL()q I}c#iF)Ž]";tr_ `/ #-^O;4s=evxsse޼OHj7+[vp-@YNKsΑSO;]{GMM$e;Hu9-~'ƍ+.p*LK@IsG%۶jժ5k믾^_$4 ZNN<$Mkyy8׽>)_4س]feaYr5^ d]r2&TxFW@D-h :mܰ[u֯z \~ŕ^]=b=xE Y,/-[/0pzwJy(H@ɣ?n58٧w$'k;=n{7d2>OÙ Ky:u":k|np-5kϏ2<zQ^*HE97xX/'[Kg[qԃ4qR.=ᕕf˃a/@>Cozu{`Æuo =W2 H@e_!]rIh~(=7rQ0fT3l,YRh y48ؽ'rRj5Jw\Fګ1ԪU[vI^6mG9H@aD+j~=NՅ> 8.V4٦D2HE)ʱ zc :C90?+pNk0.tr;{ YO.eu.Gy j1JT& 睗wrQgr5 &Q ?#LoUoKʪիmϾ7o|)SDn!p0&hKeRI϶ t]|WOéHG9'xX'#G@ \wr?aP =#gҋxcO>uE?$67{Rٲq U ]dQ ׋ʕ+ˡ6mڀ[xjr\8 ^S-rr׽J:ul6{Yt.QN©0-.'Zvw;f^ V^=2<3h,ģ` і|?R|9{㣑mttvrME;v,+V/8;+ט()'1?)/Ex@ XD{iXk^w&N4]ivh)aWf" HWyyd{Inn5[m[[᧞yVʐR^B8I@*ˉ6.{C(Mo?@D('J\RY^4+-뮹:lv?ݷ-O5kfAf,ţ` MіmP7$H1 5{mHoM mes͚n0޻'ʋ_T ēGI9j+H 8%^!rұSg9v6˨>7QtƍfZv74l%Hu9),Zyi*|R)k93;u֭S.y%hZb)/Zϼ0~Su~{^Aw1)]:|e(/NtěGIrl#}l ЊwGA|9eJG!-ܳNA3Vz^yEzqiC"^{IZ/0 dB9 w|{^[D9 'ŴT~^DZ7O h{rfǎ6hi[уr+r@W2]יt>`2_)'SoϜ1*!D\v-RS}sZuVJ9 *1ltxDW@D[?Dt.X 50n Ө㜢 t -agiݕwKR^i1-.'z?v'ɐꆵM|HwqW<5%m>7lhJևa+\)/W^mNSaH,Z4TW5kns =iھ= %,S ro('ʱ^"N0x};=C)X\ڞW VKxi۶m>__fM;}ɒ%oE& P@&H瞤ϧ5N@&>0C9 0 RN/o9('T@<$ 1]b]~ay7JҖ]Űrb8, >Q"C⟧$W^ܾc=xE ./a˖-fS^0!\NfUO@&m[&fN+ S^q3vh5{_}@D賍XUNb=N8>ح[^O-ny}|YvV 칅rb8. >V(i]qU}g˙꼾O^nvW)aYtczv{mشS^"i1=Y*'Zp k}M_/εPϛn)&Q7-T ]Xz`W=? ;]+4q^ XK})HϹ^t~_~ٮ^reqeMںu;-;{L6S^BI@Ix};L9 "tK!9P;͛e/xh|IHw9w_&2y$?~q'ʅ_"vNvve SN4<@շ|-}|oV,[f6[bEi"w#"]`_eZ#WtpG}O zZ6lhS×}@vdB-|ģ: @r⩏<_?i@zg @kX;ᩦK|:b,]: )HwyzG<*s̱?`ϛg3^{eWZe, H +0999vD<_e)')Hwyɻ8Xrsd osD]C ,+ v_ >sD=}ɧHҥmƍvEgkޡ-[3o6_hB~?;p~XH@&xI9GuR!@q*GF n;ڷw^5 /#MТfK ŋe NUHX/H6mMl=. }˛i=XW^{cc{նM=eaX]N~5`~5}]~A wr$xM@ˉ0ҥ:(e3z'ʉ_d dJ9Pzi/\~_FK'k)'zL< :#;I=>HE+ߴrM۶2rv=mX^=vG9q&K Gy|+z @r⩏<*V2qf[Hu5j֔kMK.hv6HJLO@ˋiuvkr'w!! F(rm?('AƓ-W_V_?D¥7=w Ey `$=U>l/ZPj[t>oxsL=2>wh92{)aH9)Hy$IQ_ fݮvc+֮YSآ| ttgl2$`ٽR7e{suz\"gͲӴX'Yy7@ ^gm$yr˃'?[F('!$Q $ڬmݺŞSN 'S ʉ>إ]7Kz>;w\wն 2h+KL)v]aZ8!Ob?oA%/Mݠ2fϞF yw]6i (dG xCSҜ9yVX-5{&o2 :sVky]z]h^.vm-Hw96oa0QN"'[ E.dꈳᒖR{nH=.Ky 'ƴd S^5[.uZku^P]Z/A*W^v [$xM@`> hڲeKp7N9( R4jwQ#}71oN,wXQӌTawT`јF:X6o tȘga2Irox}yRS ,櫯*OLz9 TNmIQX7 *'%A$5k6^d҄ !7%#rI RVM\=LpOkHr9LKyuD+ V^N2͊J9HÌ4 $|ēGԸ`,w8SPL7 Qh,%/ 1?]*'r8DDTyE@@@@2L e t    d@"KgN8@@@@@@@@@@HAĩPf          dAfp(          B T(@@@@@@@@@@2H z38@@@@@@@@@@R!@q*          $@q            8@@@@@@@@@@  8 @@@@@@@@@@TD e          @ DAo          @*"N2@@@@@@@@@@@ "Π7CA@@@@@@@@@@ B}           AgЛ           S>@@@@@@@@@@ 3P@@@@@@@@@@HAĩPf          dAfp(          B T(@@@@@@@@@@2H z38@@@@@@@@@@R!@q*          $@q   p 09;.T<{ZTI^$ d-)Nk^댠j`c m\*HwH.6+>ϭF9NƇ-Y˺g[[#iF*4?i@@@@@@@@$_ e          @ D\@7           p5]pk]^~ҨeRͽzNy˪^"4ɠ'}9?֤}'%W D3p@@@@@`5zjR|Dθ9 mL.    _y}VӚ;+1*0cWuVu=YQ2+JMA?i'kUn$oq@@@@j},cŖn&f<_o*F@@@HXtax7i&fa֎@@@@@ _67[&BNWfoιbƹ(@@@@` D\w^L7i_g*4yJӳ:@p~{yZs    X`6㺵Š朧tCń;HWO8^}@@@@ nNvu~-kƶoUn:VH:ZӤ]_eҘ9alHykZ땱rݼUks ֨Ԟc8so+b    u%0Ԫ֨ڙmtxP:q25@Xܦ:od iYب NuN-t:焸|&ۤ7.h^=B\b&-3ǡNn7_,z[똉:wk@?ؾITEoW|21P^mܕ8/kzdڝ-yxwμ= T{N׷%R(a}sggȽݯgd2S`ܜjv]+MI.DBR1m|=C@@@@XRMx",_uSX6x3LNsF@8E.9ٍuZ*3 6Չg hMY󈏷BK]Kw'ovL,.XXkƗ.fٗ͢$&q.7&$)i81dK*]4#RK     p 3n< y?g|6̏I܏Q)L1_x$WTC~+T`KBW6~Mk*'JxCƸ4vl;0ݞg"x$GXf~Mi .V7~GCle5&PjӗnJbʵ-zpBbx|հy>Kd#c;T衿ٯ n5hmȌ_1.W=~Ǐ 2|@@@@JUޢ?֟f势KBfr,Y!ړC^UtX4[#=evH"5|IR$; ى.;=f09L;&k"&~X89oIQr2_{9[+Ӵ;b~9Ύ6sŬH=6qܕ&lf=ČR_ҐϞgO;ߠOuqv<+e]6c$k$P:E@@@ȫI1?YK /tM{ݝNxv6w[DV R}'~ЪǝFzOz UwiCYڦ`t`=:Vn57]znMozXkcUW[yN:~ ELe}z/rd.YR+hQc\qNaq۪i$NSN?:tª̡sڸe\vp,QD eZl:8Nq籋    0c3`g}&sbműUcw_qscn]g> =hϵ=.^8Vrg7Ğ v8e֝neb{lW9jKh2g={b&&Gux,k7OL\$BE~=&w.q}]I} vckV7Ofli{Nu*5+Zv~Ͻ@lWw<>I9D@@@@`Nn,5pƥ]#{|^5D":@l:lyxQ,\i۰^oy0Mq]Čc%.g@]u"/Ez}".2 wtVv'ncٔ@lw8ڪbo+'^ģ<@lKqLfrO#   LU*6kuWy,ج&{4݀EUJJoNFM_P֛+Tش4n~}P OgLV woof%㇣3թܲ 5jh2O Ď ~IzWeV%RUi>}3},Fݗϧ@@@@[ %>YS2:m?tr'NM,j<097t0]2}8w1~P'-͈Yz;V̬B۞R>'2/6رIwV,:D}"@@@@ Dw Lp- GwUך&2L-mکuMnJᬗ.m)әnT֙Lrtd"xHEzhwdwWݟTlӢ&p̆    yK_|v TW#N!f9x`٪U(1c'&kcY'cq)cM=fӎofcn,=n"   \cΗ?uˡ^Sye/g(=mhvC>ag,˄?Sbh?S;/?lq :cw'[=OW X#)13.#    P|8/y'⬳1ˡYbenLoH\z̓x's\q:vbUdӏG^+Z*YVC4A@@@+,s5B:Њ?]Z 7g9/:IVUg*oz%I@l ډWph[ 6Iǟ9^_9 Ѻ֜։brc:v[#ܩ#F:    *?LxQ"zyH8㆗mGfȗŊYe{<`c ݊j1c օ:q[Szng@@@@ 8v#ƿ׹:@Mtq6JsXFSؕfkR+4D.MSheDqd/wkNqʜ!f)'SǓ+زW0ܫf9vga>Ml_{%s    |FiAa ,2nuO%rZt>)9skdx`ƌ=cWo1J;!㑀w_O=g4H|۶ʻ8ZjC  GxnTr4z.Y91ZtNbg5OoAN\.tXJ'WwFZwbxN5VE;?>%_Ww&Twǟn|C Ƹ=ze5ט;5r}v|SG^tw&լUݣ;u`SHL:~s9i%Oռg畯^t'=zS53A;➻6?դ _9F@@@@`> L=tGNر NX<o$&/EҶ ]E' 8ٜ#ŸB|tdh{ؑ651Y6ms?ԁmY͏$)6v~Yo=fl    -Gw.ѧ54 5yZmkVjxtMDz8mc0. H ~nuۅ>>|%Zb|/VsR;sd]"5SoP=s~XuSF|Du]#ZYq*o~?ծȳ§_ƆyyQ:Ң$8]%1    L [\M:oL^^#Y덏#ԓWA@E5fQw?{ k'sd)W]L}medkXRxB)qs#3S5g??Rey?6@@@@#Jyfa+ip&w4V?=I6I x5lM|ɧùځs+q6>gVNȠzwL(99rFWsvKڙ-+6+azM8LW?֐( )pUT fFXPznwʎ54y_םq|2.O3oվ~ {"If:T~?34@@@@y$5-x/μڦONtﮧ^8|tf4IJ׹0;*qGOc :"1O5fԃ:q.Nnßn{")W.    |yŊH H]OSfJ6 fMncKC#a G@ꥼUZ+TjWvgL$}6vl3tyPK8ǷAEzT8tZi.U2ɮ046oU~L߆Ụwѝx?ݬBa uh_nf͉ZIDAT__86PPsy4?͊R    0M%%ʲŋCn-_A!@@@@rgܘ$\i|y]YbSյpBg@@@@C lb$gӡnuTcskxv%9@@@@kV qE׬3jUW$✶jYgkwNi     0|յq$؜f     0}oǙ~5m>hXOJ8@@@@@Xp/Y h]L]~syG    S1_^b<22xt)3 xR;͆     0}EC'҉@쌾@@@@@ ,f7iQ|ZrM["7][     (1Ib     0_U%mEՉx@@@@@kE`:1LLčZOC@@@@D`dd$rEW~L{),     ׄtb_|E^&W:C@@@@H×.vŏ>eYr?9@@@@@(cnIENDB`fastnetmon-1.2.8/docs/images/network_map.png000066400000000000000000000767171472727706000211670ustar00rootroot00000000000000PNG  IHDRUP sBIT|d pHYs   IDATxy|np2"Dِ@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.2.8/src/000077500000000000000000000000001472727706000145035ustar00rootroot00000000000000fastnetmon-1.2.8/src/.clang-format000066400000000000000000000037731472727706000170700ustar00rootroot00000000000000--- Language: Cpp AccessModifierOffset: 0 AlignAfterOpenBracket: true AlignConsecutiveAssignments: true AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false ColumnLimit: 120 CommentPragmas: '' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 0 # Of line should be splitted to two lines we are using this additional indent ContinuationIndentWidth: 4 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 2 NamespaceIndentation: None ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true PenaltyBreakBeforeFirstCallParameter: 100 PenaltyBreakComment: 100 PenaltyBreakFirstLessLess: 0 PenaltyBreakString: 100 PenaltyExcessCharacter: 1 PenaltyReturnTypeOnItsOwnLine: 20 PointerAlignment: Left SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 4 UseTab: Never ... fastnetmon-1.2.8/src/.clang_formatter_excludes000066400000000000000000000011241472727706000215450ustar00rootroot00000000000000netmap_plugin/netmap_includes/net/netmap.h netmap_plugin/netmap_includes/net/netmap_user.h nlohmann/json.hpp simple_packet_capnp/simple_packet.capnp.c++ simple_packet_capnp/simple_packet.capnp.h fastnetmon.pb.h fastnetmon.pb.cpp fastnetmon.grpc.pb.h actions/gobgp.grpc.pb.h actions/gobgp.grpc.pb.cpp actions/gobgp.pb.h actions/attribute.pb.h fmt/compile.h fmt/core.h fmt/format-inl.h fmt/format.h build traffic_data.pb.h traffic_data.pb.cc packaging/FreeBSD/files/patch-src_fast__endianless.hpp packaging/FreeBSD/files/patch-src_fast__library.cpp packaging/FreeBSD/files/patch-src_fastnetmon.cpp fastnetmon-1.2.8/src/CMakeLists.txt000066400000000000000000001461701472727706000172540ustar00rootroot00000000000000# We upgraded it to support https://cmake.org/cmake/help/latest/policy/CMP0077.html cmake_minimum_required (VERSION 3.13) set(FASTNETMON_LIBRARIES_GLOBAL_PATH "/opt/fastnetmon-community/libraries") # We need this options for generating compile_commands.json file # It's required for clang static analyzer and autocompletion tools based on clang # PVS uses it too set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project(FastNetMon) include(GNUInstallDirs) include(CheckCXXCompilerFlag) include(CheckLibraryExists) # Enable it and fix all warnings # add_definitions ("-Wall") set (FASTNETMON_VERSION_MAJOR 1) set (FASTNETMON_VERSION_MINOR 2) set (FASTNETMON_VERSION_PATCH 8) set(HIREDIS_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/hiredis_0_14") set(LOG4CPP_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/log4cpp_1_1_4") set(LIBPCAP_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/pcap_1_10_4") set(MONGO_C_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/mongo_c_driver_1_23_0") set(CAPNP_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/capnproto_0_8_0") set(CLICKHOUSE_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/clickhouse_2_3_0") set(OPENSSL_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/openssl_1_1_1q") set(GRPC_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/grpc_1_49_2") set(LIBBPF_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/bpf_1_0_1") set(LIBELF_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/elfutils_0_186") set(PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/protobuf_21_12") set(ABSL_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/abseil_2024_01_16") set(BOOST_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/boost_1_81_0") set(GCC_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/gcc_12_1_0") set(LIB_CPP_KAFKA_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/cppkafka_0_3_1") set(LIB_RDKAFKA_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/rdkafka_1_7_0") set(GTEST_INSTALL_PATH "${FASTNETMON_LIBRARIES_GLOBAL_PATH}/gtest_1_13_0") # Enable time profiling for compilation phase # https://stackoverflow.com/questions/5962285/cmake-compilation-statistics-per-transation-unit # set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time") # -Wunused includes more warnings than -Wall # In order to get a warning about an unused function parameter, you must either specify -Wextra -Wunused (note that -Wall implies -Wunused), or separately specify -Wunused-parameter. # TODO: return -Wunused-parameter and address all warning later, I started it but did not finish as we have too many of them # catch-value is documented here: https://patchwork.ozlabs.org/project/gcc/patch/tkrat.8c7b4260a533be2f@netcologne.de/#1680619 add_definitions("-Wreorder -Wunused -Wparentheses -Wimplicit-fallthrough -Wreturn-type -Wuninitialized -Winit-self -Wmaybe-uninitialized -Wcatch-value=3 -Wclass-memaccess") # On Windows we need to build libgcc and libstdc++ statically to avoid need to carry dlls with us if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(CMAKE_CXX_STANDARD_LIBRARIES "-static-libgcc -static-libstdc++ ${CMAKE_CXX_STANDARD_LIBRARIES}") endif() # We need this to avoid dependency on libwinpthread-1.dll # Details: https://cmake.org/pipermail/cmake/2019-June/069611.html if (MINGW) set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive") endif() # We use this approach instead of following: # set (CMAKE_CXX_STANDARD 20) # Because it allows us to specify intermediate releases and releases not yet supported by cmake set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++20") # With this flag we can enable GoBGP build via console: cmake .. -DENABLE_GOBGP_SUPPORT=ON option(ENABLE_GOBGP_SUPPORT "Enable GoBGP support build" ON) # It can be disabled this way: cmake .. -DENABLE_PCAP_SUPPORT=OFF option(ENABLE_PCAP_SUPPORT "Enable PCAP support" ON) # We need to explicitly link with libabsl. # We do not use it directly but gRPC uses it for libgpr and we need to link with absl explicitly to avoid linker errors like this: # libgpr.so: undefined reference to symbol '_ZN4absl12lts_202103245Mutex4LockEv' # /usr/lib64/libabsl_synchronization.so.2103.0.1: error adding symbols: DSO missing from command line option(LINK_WITH_ABSL "Enable optonal linking with ABSL" OFF) # Kafka support is optional and we do not enable it for build by default option(KAFKA_SUPPORT "Enables Kafka support" OFF) # We need to add it into include path as gRPC uses it include path include_directories("${ABSL_INSTALL_PATH}/include") option(DO_NOT_USE_SYSTEM_LIBRARIES_FOR_BUILD "Disables use of libraries from system path" OFF) if (DO_NOT_USE_SYSTEM_LIBRARIES_FOR_BUILD) # We need to avoid using system path for libraries and includes search because we ship specific versions of our own libraries in package # And we need to avoid implicit fallbacks to system libraries as it will break dependencies set(DISABLE_DEFAULT_PATH_SEARCH_VAR "NO_DEFAULT_PATH") else () # Disable this logic and allow any paths set(DISABLE_DEFAULT_PATH_SEARCH_VAR "") endif() if (DO_NOT_USE_SYSTEM_LIBRARIES_FOR_BUILD) message(STATUS "Build with custom Boost") set(Boost_NO_SYSTEM_PATHS ON) set(BOOST_INCLUDEDIR "${BOOST_INSTALL_PATH}") set(BOOST_LIBRARYDIR "${BOOST_INSTALL_PATH}/lib/") SET(Boost_DIR "${BOOST_INSTALL_PATH}/lib/cmake/Boost-1.81.0/") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-deprecated-declarations") set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${BOOST_INSTALL_PATH}/lib;${GCC_INSTALL_PATH}/lib64") endif() # We use hardcoded RPATH for our libraries only when we compile against our custom libraries if (DO_NOT_USE_SYSTEM_LIBRARIES_FOR_BUILD) # 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) # We need to manually set RPATH to each of our custom libraries # Otherwise our binaries will not able to find them as we use non standard path SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${HIREDIS_CUSTOM_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${LOG4CPP_CUSTOM_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${MONGO_C_CUSTOM_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${MONGO_C_CUSTOM_INSTALL_PATH}/lib64") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${GRPC_CUSTOM_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH}/lib64") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${CAPNP_CUSTOM_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${OPENSSL_CUSTOM_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${CLICKHOUSE_CUSTOM_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${LIBBPF_CUSTOM_INSTALL_PATH}/lib64") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${LIBELF_CUSTOM_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${LIB_CPP_KAFKA_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${LIB_RDKAFKA_INSTALL_PATH}/lib") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${LIBPCAP_CUSTOM_INSTALL_PATH}/lib") else() # We do not need any RPATH alterations when we want to link with system libraries (i.e. upstream builds for Debian or RedHat family) endif() message(STATUS "C++ default compilation flags: ${CMAKE_CXX_FLAGS}") message(STATUS "C++ release compilation flags: ${CMAKE_CXX_FLAGS_RELEASE}") message(STATUS "C++ debug compilation flags: ${CMAKE_CXX_FLAGS_DEBUG}") set(FASTNETMON_PROFILER OFF) set(FASTNETMON_PROFILE_FLAGS "-g -pg") # set(CMAKE_BUILD_TYPE DEBUG) 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() execute_process(COMMAND sh -c ". /etc/os-release; echo $ID" OUTPUT_VARIABLE OS_ID ERROR_QUIET) ### 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) # Short 8 symbol commit execute_process(COMMAND git rev-list HEAD COMMAND head -n 1 COMMAND cut -c1-8 OUTPUT_VARIABLE GIT_LAST_COMMIT_HASH_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Commit hash: ${GIT_LAST_COMMIT_HASH_SHORT}") set(FASTNETMON_APPLICATION_VERSION "${FASTNETMON_VERSION_MAJOR}.${FASTNETMON_VERSION_MINOR}.${FASTNETMON_VERSION_PATCH} ${GIT_LAST_COMMIT_HASH_SHORT}") # Set standard values which work for majority of platforms set(FASTNETMON_PID_PATH "/var/run/fastnetmon.pid") set(FASTNETMON_CONFIGURATION_PATH "/etc/fastnetmon.conf") set(FASTNETMON_LOG_FILE_PATH "/var/log/fastnetmon.log") set(FASTNETMON_ATTACK_DETAILS_FOLDER "/var/log/fastnetmon_attacks") set(FASTNETMON_NOTIFY_SCRIPT_PATH_DEFAULT "/usr/local/bin/notify_about_attack.sh") set(FASTNETMON_NETWORK_WHITELIST_PATH "/etc/networks_whitelist") set(FASTNETMON_NETWORKS_LIST_PATH "/etc/networks_list") set(FASTNETMON_BACKTRACE_PATH "/var/log/fastnetmon_backtrace.dump") set(FASTNETMON_WHITELIST_RULES_PATH "/etc/whitelist_rules") # For FreeBSD based platforms we need to adjust them if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly") set(FREEBSD_DEFAULT_PREFIX "/usr/local") set(FASTNETMON_PID_PATH "/var/run/fastnetmon/fastnetmon.pid") set(FASTNETMON_CONFIGURATION_PATH "${FREEBSD_DEFAULT_PREFIX}/etc/fastnetmon.conf") set(FASTNETMON_LOG_FILE_PATH "/var/log/fastnetmon/fastnetmon.log") set(FASTNETMON_ATTACK_DETAILS_FOLDER "/var/log/fastnetmon_attacks") set(FASTNETMON_NOTIFY_SCRIPT_PATH_DEFAULT "${FREEBSD_DEFAULT_PREFIX}/bin/notify_about_attack.sh") set(FASTNETMON_NETWORK_WHITELIST_PATH "${FREEBSD_DEFAULT_PREFIX}/etc/networks_whitelist") set(FASTNETMON_NETWORKS_LIST_PATH "${FREEBSD_DEFAULT_PREFIX}/etc/networks_list") set(FASTNETMON_BACKTRACE_PATH "/var/log/fastnetmon/fastnetmon_backtrace.dump") endif() configure_file(fast_platform.h.template "${PROJECT_SOURCE_DIR}/fast_platform.hpp") # Use new Memory Model Aware Atomic Operations # You could enable it using: cmake .. -DUSE_NEW_ATOMIC_BUILTINS=ON # We use it for exotic platforms where we have no specific functions in atomics library: https://salsa.debian.org/debian/fastnetmon/-/blob/master/debian/rules#L11 if (USE_NEW_ATOMIC_BUILTINS) message(STATUS "Will use new memory model aware atomic builtins") add_definitions(-DUSE_NEW_ATOMIC_BUILTINS) endif() # Be default we do not link with it but we need it on platforms without native support for atomic operations # On these platforms we will use logic emulated by libatomic set(LINK_WITH_ATOMIC_LIBRARY OFF) CHECK_CXX_SOURCE_COMPILES(" #include int main() { uint64_t x = 1; __atomic_add_fetch(&x, 0, __ATOMIC_RELAXED); return x; } " HAVE__ATOMIC_ADD_FETCH) if (HAVE__ATOMIC_ADD_FETCH) message(STATUS "We have __atomic_add_fetch on this platform") else() message(STATUS "We have no __atomic_add_fetch, will try linking with libatomic") check_library_exists(atomic __atomic_add_fetch_8 "" HAVE_LIBATOMIC) if (HAVE_LIBATOMIC) message(STATUS "Linked with atomic library") set(LINK_WITH_ATOMIC_LIBRARY ON) else() message(STATUS "We have no support for __atomic_add_fetch in atomic library, skip linking") endif() endif() CHECK_CXX_SOURCE_COMPILES(" #include int main() { uint64_t x = 1; __sync_fetch_and_add(&x, 1); return x; } " HAVE__SYNC_FETCH_AND_ADD) if (HAVE__SYNC_FETCH_AND_ADD) message(STATUS "We have __sync_fetch_and_add on this platform") else() # We know that it happens for mipsel platform due to https://reviews.llvm.org/D45691 message(STATUS "We have no __sync_fetch_and_add on this platform, will try linking with libatomic") check_library_exists(atomic __sync_fetch_and_add_8 "" HAVE_LIBATOMIC_SYNC_FETCH_AND_ADD) if (HAVE_LIBATOMIC_SYNC_FETCH_AND_ADD) message(STATUS "Linked with atomic library") set(LINK_WITH_ATOMIC_LIBRARY ON) else() message(STATUS "We have no support for __sync_fetch_and_add in atomic library, skip linking") endif() endif() option(ENABLE_NETMAP_SUPPORT "Enable Netmap support" OFF) CHECK_CXX_SOURCE_COMPILES(" int main() { __atomic_thread_fence(__ATOMIC_RELEASE); __atomic_thread_fence(__ATOMIC_ACQUIRE); return 0; } " HAVE_ATOMIC_THREAD_FENCE) # If we do not have it then we need to disable it if (NOT HAVE_ATOMIC_THREAD_FENCE) set(ENABLE_NETMAP_SUPPORT OFF) message(STATUS "Your system does not support __atomic_thread_fence, disabled Netmap plugin support") endif() if (ENABLE_NETMAP_SUPPORT) message(STATUS "We will build Netmap support for you") add_definitions(-DNETMAP_PLUGIN) endif() # It's enabled by default but can be disabled using: # cmake .. -DENABLE_CAPNP_SUPPORT=OFF option(ENABLE_CAPNP_SUPPORT "Enable Cap'N'Proto support build" ON) if (ENABLE_CAPNP_SUPPORT) message(STATUS "We will build Cap'N'Proto support") find_program(CAPNP_BINARY capnp PATHS "${CAPNP_CUSTOM_INSTALL_PATH}/bin" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (CAPNP_BINARY) message(STATUS "Found capnp compiler: ${CAPNP_BINARY}") else() message(FATAL_ERROR "Can't find capnp compiler") endif() # We need to explicitly provide PATH for capnp to allow it to find capnpc-c++ SET(CAPNP_ENVIRONMENT "LD_LIBRARY_PATH=$ENV{LD_LIBRARY_PATH}" "PATH=$ENV{PATH}:${CAPNP_CUSTOM_INSTALL_PATH}/bin") # We cannot pass environment variables before tool call on Windows and we actually do need it if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") SET(CAPNP_ENVIRONMENT "") endif() # Generate capnp bindings ADD_CUSTOM_COMMAND( OUTPUT ${PROJECT_SOURCE_DIR}/simple_packet_capnp/simple_packet.capnp.c++ DEPENDS ${PROJECT_SOURCE_DIR}/simple_packet_capnp/simple_packet.capnp COMMAND ${CAPNP_ENVIRONMENT} ${CAPNP_BINARY} compile --output c++:${PROJECT_SOURCE_DIR}/simple_packet_capnp --src-prefix=${PROJECT_SOURCE_DIR}/simple_packet_capnp ${PROJECT_SOURCE_DIR}/simple_packet_capnp/simple_packet.capnp COMMENT "Build Cap'n'Proto binding for C++" ) add_library(simple_packet_capnp STATIC simple_packet_capnp/simple_packet.capnp.c++) endif() # It's disabled by default as we have no CLickhouse support in system repos on many platforms but can be enabled using: # cmake .. -DCLICKHOUSE_SUPPORT=ON option(CLICKHOUSE_SUPPORT "Enable Cap'N'Proto support build" OFF) if (CLICKHOUSE_SUPPORT) find_library(CLICKHOUSE_LIBRARY_PATH NAMES clickhouse-cpp-lib PATHS "${CLICKHOUSE_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (CLICKHOUSE_LIBRARY_PATH) message(STATUS "Found libclickhouse library: ${CLICKHOUSE_LIBRARY_PATH}") # Enable Clickhouse define in code add_definitions(-DCLICKHOUSE_SUPPORT) else() message(FATAL_ERROR "We could not find libclickhouse library") endif() include_directories("${CLICKHOUSE_CUSTOM_INSTALL_PATH}/include") endif() # Our LPM library add_library(patricia STATIC libpatricia/patricia.cpp) # Graphite metrics add_library(graphite_metrics STATIC metrics/graphite.cpp) target_link_libraries(fastnetmon graphite_metrics) # InfluxDB metrics add_library(influxdb_metrics STATIC metrics/influxdb.cpp) target_link_libraries(fastnetmon influxdb_metrics) # Clickhouse metrics if (CLICKHOUSE_SUPPORT) add_library(clickhouse_metrics STATIC metrics/clickhouse.cpp) target_link_libraries(clickhouse_metrics ${CLICKHOUSE_LIBRARY_PATH}) target_link_libraries(fastnetmon clickhouse_metrics) endif() 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_fields/ipfix_rfc.cpp) add_library(bgp_protocol STATIC bgp_protocol.cpp) # Here we store some service code for getting IP protocol name by number add_library(iana_ip_protocols STATIC iana_ip_protocols.cpp) # BGP Flow Spec add_library(bgp_protocol_flow_spec STATIC bgp_protocol_flow_spec.cpp) target_link_libraries(bgp_protocol_flow_spec iana_ip_protocols bgp_protocol) # Our filtering library add_library(filter STATIC filter.cpp) target_link_libraries(filter bgp_protocol bgp_protocol_flow_spec) # Our logic library add_library(fastnetmon_logic STATIC fastnetmon_logic.cpp) # API library add_library(fastnetmon_api STATIC api.cpp) CHECK_CXX_SOURCE_COMPILES(" #include int main() { return TPACKET_V3; } " HAVE_TPACKET_V3) if (${HAVE_TPACKET_V3}) message(STATUS "Your system has support for AF_PACKET v3") set (ENABLE_AFPACKET_SUPPORT ON) else() message(STATUS "Your system does not support AF_PACKET v3, disabled it") 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() # We need to check that kernel headers actually support it as it's relatively new thing CHECK_CXX_SOURCE_COMPILES(" #include int main() { bpf_stats_type my_bpf_type; return 1; } " HAVE_BPF_STATS_TYPE) if (${HAVE_BPF_STATS_TYPE}) message(STATUS "Kernel has enum bpf_stats_type declared") else() message(STATUS "Kernel does not have enum bpf_stats_type declared. Try to declare our own to address libbpf issue: https://github.com/libbpf/libbpf/issues/249") add_definitions(-DDECLARE_FAKE_BPF_STATS) endif() # We need to check that kernel headers actually include it CHECK_CXX_SOURCE_COMPILES(" #include int main() { bpf_link_type my_bpf_type; return 1; } " HAVE_BPF_LINK_TYPE) if (${HAVE_BPF_LINK_TYPE}) message(STATUS "Kernel has enum bpf_link_type declared") else() message(STATUS "Kernel does not have enum bpf_link_type declared. Try to declare our own to address libbpf issue: https://github.com/libbpf/libbpf/issues/249") add_definitions(-DDECLARE_FAKE_BPF_LINK_TYPE) endif() # We enable XDP plugin build for all platforms which support it # It can be disabled manually using flag: -DENABLE_AF_XDP_SUPPORT=FALSE option(ENABLE_AF_XDP_SUPPORT "Enables build for AF_XDP" ON) CHECK_CXX_SOURCE_COMPILES(" #include int main() { return 1; } " HAVE_AF_XDP) # If XDP build enabled then we need to confirm that system has support for it if (${ENABLE_AF_XDP_SUPPORT}) if (${HAVE_AF_XDP}) message(STATUS "Your system has support for AF_XDP and we will build XDP plugin") else() # It may be old Linux, macOS, FreeBSD or Windows message(STATUS "Your system does not support AF_XDP, disabling compilation of XDP plugin") set (ENABLE_AF_XDP_SUPPORT FALSE) endif() endif() if (ENABLE_AF_XDP_SUPPORT) add_definitions(-DFASTNETMON_ENABLE_AF_XDP) add_library(xdp_plugin STATIC xdp_plugin/xdp_collector.cpp) set(ENABLE_LIBBPF_SUPPORT TRUE) set(ENABLE_LIBELF_SUPPORT TRUE) endif() if (KAFKA_SUPPORT) # We need to enable it explicitly add_definitions(-DKAFKA) # cpp-kafka uses these header files from their header too include_directories("${LIB_RDKAFKA_INSTALL_PATH}/include") # cppkafka find_library(LIBKAFKA_CPP_LIBRARY_PATH names "cppkafka" PATHS "${LIB_CPP_KAFKA_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) include_directories("${LIB_CPP_KAFKA_INSTALL_PATH}/include") if (NOT LIBKAFKA_CPP_LIBRARY_PATH) message(FATAL_ERROR "Could not find cppkafka library") else() message(STATUS "Will use cppkafka library from ${LIBKAFKA_CPP_LIBRARY_PATH}") endif() endif() if (ENABLE_LIBELF_SUPPORT) # We do not use it directly but we need it as dependency for libbpf # include_directories("${LIBELF_CUSTOM_INSTALL_PATH/include") find_library(LIBELF_LIBRARY_PATH names "elf" PATHS "${LIBELF_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (NOT LIBELF_LIBRARY_PATH) message(FATAL_ERROR "Could not find libelf library") else() message(STATUS "Will use libelf library from ${LIBELF_LIBRARY_PATH}") endif() endif() if (ENABLE_LIBBPF_SUPPORT) include_directories("${LIBBPF_CUSTOM_INSTALL_PATH}/include") find_library(LIBBPF_LIBRARY_PATH NAMES "bpf" PATHS "${LIBBPF_CUSTOM_INSTALL_PATH}/lib64" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (NOT LIBBPF_LIBRARY_PATH) message(FATAL_ERROR "Could not find libbpf library") else() message(STATUS "Will use libbpf from ${LIBBPF_LIBRARY_PATH}") endif() endif() ### Look for libpcap find_path(LIBPCAP_INCLUDES_FOLDER NAMES pcap.h PATHS "${LIBPCAP_CUSTOM_INSTALL_PATH}/include" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) find_library(LIBPCAP_LIBRARY_PATH NAMES pcap PATHS "${LIBPCAP_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) 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() if (ENABLE_AF_XDP_SUPPORT) target_link_libraries(xdp_plugin ${LIBBPF_LIBRARY_PATH} ${LIBELF_LIBRARY_PATH}) endif() # Library with data types for parsing network structures add_library(network_data_structures STATIC network_data_structures.cpp) # Speed counters lib add_library(speed_counters STATIC speed_counters.cpp) target_link_libraries(speed_counters fast_library) # Our new parser for parsing traffic up to L4 add_library(simple_packet_parser_ng STATIC simple_packet_parser_ng.cpp) target_link_libraries(simple_packet_parser_ng network_data_structures) # Our own sFlow parser library set_source_files_properties(libsflow/libsflow.cpp PROPERTIES COMPILE_FLAGS -pedantic) add_library(libsflow STATIC libsflow/libsflow.cpp) # sFlow plugin add_library(sflow_plugin STATIC sflow_plugin/sflow_collector.cpp) # Link sFlow plugin with new traffic parser target_link_libraries(sflow_plugin simple_packet_parser_ng) # Link sFlow plugin with libsflow target_link_libraries(sflow_plugin libsflow) # Netflow templates add_library(netflow_template STATIC netflow_plugin/netflow_template.cpp) # netflow library add_library(netflow STATIC netflow_plugin/netflow.cpp) # netflow plugin add_library(netflow_plugin STATIC netflow_plugin/netflow_collector.cpp) target_link_libraries(netflow_plugin ipfix_rfc netflow netflow_template) if (ENABLE_PCAP_SUPPORT) # pcap plugin add_library(pcap_plugin STATIC pcap_plugin/pcap_collector.cpp) target_link_libraries(pcap_plugin ${LIBPCAP_LIBRARY_PATH}) endif() find_package(Threads) add_library(exabgp_action STATIC actions/exabgp_action.cpp) if (LINK_WITH_ABSL) find_package(absl REQUIRED ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) # TODO: check that we actually found it. Otherwise trigger fatal erorr endif() if (ENABLE_PCAP_SUPPORT) add_definitions(-DENABLE_PCAP) endif() if (ENABLE_GOBGP_SUPPORT) add_definitions(-DENABLE_GOBGP) # GoBGP client library add_library(gobgp_client STATIC gobgp_client/gobgp_client.cpp) add_library(gobgp_action STATIC actions/gobgp_action.cpp) # We use find_package for Windows as our approach for *nix platforms leads to bunch of linking errors if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") # Will be great to use this approach for all builds but it's relatively tricky to accomplish # Debian 11 has no cmake files in official gRPC packages and only Debian Sid got them: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1006237 # gRPC bug tracker entry: https://github.com/grpc/grpc/issues/29977 # https://packages.debian.org/sid/amd64/libgrpc-dev/filelist # Fedora has cmake files: https://packages.fedoraproject.org/pkgs/grpc/grpc-devel/fedora-38.html#files # Epel 9 has cmake files: https://packages.fedoraproject.org/pkgs/grpc/grpc-devel/epel-9.html#files # Both latest Ubuntu 22.04 and 22.10 does not offer it in official package: https://packages.ubuntu.com/kinetic/amd64/libgrpc-dev/filelist # As Ubuntu and Debian our main platforms we will keep old logic for them until LTS / stable versions of Ubuntu and Debian receive it # We need to offer smooth developer experience and allow options to build FastNetMon at least on these platforms without using custom compiled libraries find_package(gRPC CONFIG REQUIRED ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (gRPC_FOUND) message(STATUS "Found gRPC") else() message(FATAL_ERROR "NOT Found gRPC module") endif() target_link_libraries(gobgp_client gRPC::grpc gRPC::grpc++) target_link_libraries(gobgp_action gRPC::grpc gRPC::grpc++ gobgp_client) else() find_path(GRPC_INCLUDES_FOLDER NAMES grpc/grpc.h PATHS "${GRPC_CUSTOM_INSTALL_PATH}/include" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) find_library(GRPC_LIBRARY_GRPC_PATH NAMES grpc PATHS "${GRPC_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) find_library(GRPC_LIBRARY_GPR_PATH NAMES gpr PATHS "${GRPC_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) find_library(GRPC_LIBRARY_GRPC_CPP_PATH NAMES grpc++ PATHS "${GRPC_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (GRPC_INCLUDES_FOLDER AND GRPC_LIBRARY_GRPC_PATH AND GRPC_LIBRARY_GPR_PATH AND GRPC_LIBRARY_GRPC_CPP_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_PATH}) target_link_libraries(gobgp_action gobgp_client) target_link_libraries(gobgp_client ${GRPC_LIBRARY_GRPC_PATH}) target_link_libraries(gobgp_client ${GRPC_LIBRARY_GPR_PATH}) target_link_libraries(gobgp_client ${GRPC_LIBRARY_GRPC_CPP_PATH}) message(STATUS "Found gRPC library: ${GRPC_LIBRARY_GRPC_PATH} ${GRPC_LIBRARY_GPR_PATH} ${GRPC_LIBRARY_GRPC_CPP_PATH}") else() message(FATAL_ERROR "Could not find gRPC library") endif() endif() if (LINK_WITH_ABSL) target_link_libraries(gobgp_action absl::base absl::synchronization) endif() if (DO_NOT_USE_SYSTEM_LIBRARIES_FOR_BUILD) # We add our custom path to Protobuf to top of search_list used by find_package: https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html # This approach has advantage over Protobuf_DIR which requires us to set direct path to cmake folder of custom built dependency # which resides in vendor specific folder with name lib which may be lib64 on CentOS platforms: # protobuf_21_12/lib/cmake/protobuf or protobuf_21_12/lib64/cmake/protobuf on CentOS list(APPEND CMAKE_PREFIX_PATH ${PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH}) endif() # Apparently it's required to set this flag because without this flag set it cannot find protoc when custom library path is in use # https://github.com/protocolbuffers/protobuf/issues/1931 set(protobuf_MODULE_COMPATIBLE true) # Switch to use to configuration supplied by custom Protobuf installation as it may be better find_package(Protobuf CONFIG) if (NOT Protobuf_FOUND) # Fall back to module supplied by cmake to search for Protobuf # https://cmake.org/cmake/help/latest/module/FindProtobuf.html find_package(Protobuf MODULE REQUIRED) endif() if (Protobuf_FOUND) message(STATUS "Found Protobuf ${Protobuf_VERSION}") # Empty checks are very tricky in cmake: # https://cmake.org/pipermail/cmake/2011-October/046939.html if ("${Protobuf_PROTOC_EXECUTABLE}" STREQUAL "") message(FATAL_ERROR "Protobuf was found but we did not find protoc") endif() message(STATUS "Found Protobuf compiler: '${Protobuf_PROTOC_EXECUTABLE}'") # We need to explicitly provide paths for our dependency libraries in environment variable LD_LIBRARY_PATH as we use non system path for them # CentOS uses lib64 but Debian / Ubuntu still use lib for Protobuf, that's why we keep both of them set(ENV{LD_LIBRARY_PATH} "${GCC_INSTALL_PATH}/lib64:${PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH}/lib:${PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH}/lib64:${GRPC_CUSTOM_INSTALL_PATH}/lib") message(STATUS "Protobuf include directory: ${Protobuf_INCLUDE_DIRS}") include_directories(${Protobuf_INCLUDE_DIRS}) else() message(FATAL_ERROR "NOT Found Protobuf module") endif() target_link_libraries(gobgp_action protobuf::libprotobuf) # Search for gRPC plugin for Protobuf, it's just binary find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin PATHS "${GRPC_CUSTOM_INSTALL_PATH}/bin" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (GRPC_CPP_PLUGIN) message(STATUS "Found Protobuf gRPC compiler plugin: ${GRPC_CPP_PLUGIN}") else() message(FATAL_ERROR "Can't find Protobuf gRPC compiler plugin") endif() message(STATUS "Building protobuf and gRPC mappings for C++") execute_process(COMMAND ${Protobuf_PROTOC_EXECUTABLE} -I ${PROJECT_SOURCE_DIR}/gobgp_client --grpc_out=${PROJECT_SOURCE_DIR}/gobgp_client --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} ${PROJECT_SOURCE_DIR}/gobgp_client/gobgp.proto ERROR_VARIABLE PROTOC_STDERR RESULT_VARIABLE PROTOC_RETURN_CODE OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Protoc return code for gobgp.proto gRPC: ${PROTOC_RETURN_CODE} std err: ${PROTOC_STDERR}") execute_process(COMMAND ${Protobuf_PROTOC_EXECUTABLE} -I ${PROJECT_SOURCE_DIR}/gobgp_client --cpp_out=${PROJECT_SOURCE_DIR}/gobgp_client ${PROJECT_SOURCE_DIR}/gobgp_client/gobgp.proto ${PROJECT_SOURCE_DIR}/gobgp_client/attribute.proto ERROR_VARIABLE PROTOC_STDERR RESULT_VARIABLE PROTOC_RETURN_CODE OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Protoc return code for gobgp.proto and attribute.proto Protobuf: ${PROTOC_RETURN_CODE} std err: ${PROTOC_STDERR}") message(STATUS "Building Protobuf bindings for C++ for Flow Data") execute_process(COMMAND ${Protobuf_PROTOC_EXECUTABLE} -I ${PROJECT_SOURCE_DIR}/traffic_output_formats/protobuf --cpp_out=${PROJECT_SOURCE_DIR}/traffic_output_formats/protobuf ${PROJECT_SOURCE_DIR}/traffic_output_formats/protobuf/traffic_data.proto ERROR_VARIABLE PROTOC_STDERR RESULT_VARIABLE PROTOC_RETURN_CODE OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Protoc return code for FlowData Protobuf: ${PROTOC_RETURN_CODE} std err: ${PROTOC_STDERR}") # Build Flow Data binary add_library(traffic_data_library STATIC traffic_output_formats/protobuf/traffic_data.pb.cc) add_library(protobuf_traffic_format STATIC traffic_output_formats/protobuf/protobuf_traffic_format.cpp) target_link_libraries(protobuf_traffic_format traffic_data_library) # Build gRPC and protocol buffers libraries and link they to gobgp_action add_library(gobgp_api_client_pb_cc STATIC gobgp_client/gobgp.pb.cc) add_library(gobgp_api_client_grpc_pb_cc STATIC gobgp_client/gobgp.grpc.pb.cc) # It does not work without on Windows but works fine on *nix if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") target_link_libraries(gobgp_api_client_grpc_pb_cc gRPC::grpc gRPC::grpc++) endif() target_link_libraries(gobgp_action gobgp_api_client_pb_cc) target_link_libraries(gobgp_action gobgp_api_client_grpc_pb_cc) # Add attributes add_library(attribute_pb_cc STATIC gobgp_client/attribute.pb.cc) target_link_libraries(attribute_pb_cc protobuf::libprotobuf) target_link_libraries(gobgp_action attribute_pb_cc) # FastNetMon API add_definitions(-DFASTNETMON_API) execute_process(COMMAND ${Protobuf_PROTOC_EXECUTABLE} -I ${PROJECT_SOURCE_DIR} --grpc_out=${PROJECT_SOURCE_DIR} --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} ${PROJECT_SOURCE_DIR}/fastnetmon_internal_api.proto ERROR_VARIABLE PROTOC_STDERR RESULT_VARIABLE PROTOC_RETURN_CODE OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Protoc return code for gRPC fastnetmon_internal_api.proto: ${PROTOC_RETURN_CODE} std err: ${PROTOC_STDERR}") execute_process(COMMAND ${Protobuf_PROTOC_EXECUTABLE} -I ${PROJECT_SOURCE_DIR} --cpp_out=${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/fastnetmon_internal_api.proto ERROR_VARIABLE PROTOC_STDERR RESULT_VARIABLE PROTOC_RETURN_CODE OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Protoc return code for Protobuf fastnetmon_internal_api.proto: ${PROTOC_RETURN_CODE} std err: ${PROTOC_STDERR}") add_library(fastnetmon_grpc_pb_cc STATIC fastnetmon_internal_api.grpc.pb.cc) add_library(fastnetmon_pb_cc STATIC fastnetmon_internal_api.pb.cc) add_executable(fastnetmon_api_client fastnetmon_api_client.cpp) if (LINK_WITH_ABSL) target_link_libraries(fastnetmon_api_client absl::base absl::synchronization) endif() # We use another way to specify dependencies for Windows as our standard approach clearly does not work # https://www.f-ax.de/dev/2020/11/08/grpc-plugin-cmake-support.html if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") target_link_libraries(fastnetmon_api_client gRPC::grpc gRPC::grpc++) else() target_link_libraries(fastnetmon_api_client ${GRPC_LIBRARY_GPR_PATH}) target_link_libraries(fastnetmon_api_client ${GRPC_LIBRARY_GRPC_CPP_PATH}) target_link_libraries(fastnetmon_api_client ${GRPC_LIBRARY_GRPC_PATH}) endif() 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 protobuf::libprotobuf) if (KAFKA_SUPPORT) target_link_libraries(fastnetmon ${LIBKAFKA_CPP_LIBRARY_PATH}) endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") target_link_libraries(fastnetmon gRPC::grpc gRPC::grpc++) else() target_link_libraries(fastnetmon ${GRPC_LIBRARY_GPR_PATH}) target_link_libraries(fastnetmon ${GRPC_LIBRARY_GRPC_CPP_PATH}) target_link_libraries(fastnetmon ${GRPC_LIBRARY_GRPC_PATH}) endif() target_link_libraries(fastnetmon fastnetmon_grpc_pb_cc) target_link_libraries(fastnetmon fastnetmon_pb_cc) target_link_libraries(fastnetmon protobuf::libprotobuf) endif() # example plugin add_library(example_plugin STATIC example_plugin/example_collector.cpp) if (ENABLE_NETMAP_SUPPORT) # 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) endif() # 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) # set(Boost_DEBUG ON) # Boost.System is a library that, in essence, defines four classes to identify errors. All four classes were added to the standard library with C++11. If your development environment supports C++11, you don’t need to use Boost.System. However, since many Boost libraries use Boost.System, you might encounter Boost.System through those other libraries. # Boost.System is a library that, in essence, defines four classes to identify errors. All four classes were added to the standard library with C++11. If your development environment supports C++11, you don’t need to use Boost.System. However, since many Boost libraries use Boost.System, you might encounter Boost.System through those other libraries. # TODO: we may not need system at all find_package(Boost COMPONENTS serialization thread regex program_options system REQUIRED ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if(Boost_FOUND) message(STATUS "Found Boost: ${Boost_LIBRARIES} ${Boost_INCLUDE_DIRS}") # We can get separate library paths for each library too message(STATUS "Found Boost library program_options: ${Boost_PROGRAM_OPTIONS_LIBRARY}") include_directories(${Boost_INCLUDE_DIRS}) target_link_libraries(fastnetmon ${Boost_LIBRARIES}) target_link_libraries(fast_library ${Boost_LIBRARIES}) target_link_libraries(fastnetmon_client ${Boost_PROGRAM_OPTIONS_LIBRARY}) endif() target_link_libraries(fast_library patricia) target_link_libraries(fast_library fastnetmon_pcap_format) target_link_libraries(fast_library iana_ip_protocols) # Try to find ncurses library find_package(Curses REQUIRED) if(CURSES_FOUND) message(STATUS "Found curses library: ${CURSES_LIBRARIES}") message(STATUS "Found curses includes: ${CURSES_INCLUDE_DIRS}") include_directories(${CURSES_INCLUDE_DIRS}) target_link_libraries(fastnetmon_client ${CURSES_LIBRARIES}) else() message(STATUS "We did not find curses library") 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" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) # Try to find hiredis library path find_library(HIREDIS_LIBRARY_PATH NAMES hiredis PATHS "${HIREDIS_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (HIREDIS_INCLUDES_FOLDER AND HIREDIS_LIBRARY_PATH) message(STATUS "We found hiredis library ${HIREDIS_INCLUDES_FOLDER} ${HIREDIS_LIBRARY_PATH}") add_definitions(-DREDIS) include_directories(${HIREDIS_INCLUDES_FOLDER}) target_link_libraries (fastnetmon ${HIREDIS_LIBRARY_PATH}) target_link_libraries(fastnetmon_logic ${HIREDIS_LIBRARY_PATH}) else() message(STATUS "We can't find hiredis library and will disable Redis support") endif() set(ENABLE_OPENSSL_SUPPORT TRUE) if (ENABLE_OPENSSL_SUPPORT) find_path(OPENSSL_INCLUDES_FOLDER NAMES "openssl/rsa.h" PATHS "${OPENSSL_CUSTOM_INSTALL_PATH}/include" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) # Check that we found headers if (OPENSSL_INCLUDES_FOLDER) message(STATUS "We found OpenSSL library headers: ${OPENSSL_INCLUDES_FOLDER}") include_directories(${OPENSSL_INCLUDES_FOLDER}) else() message(FATAL_ERROR "Could not find OpenSSL headers") endif() find_library(OPENSSL_LIBRARY_PATH NAMES ssl PATHS "${OPENSSL_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) find_library(OPENSSL_CRYPTO_LIBRARY_PATH NAMES crypto PATHS "${OPENSSL_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) # Check that we found libraries if (OPENSSL_LIBRARY_PATH AND OPENSSL_CRYPTO_LIBRARY_PATH) message(STATUS "We found OpenSSL library: ${OPENSSL_LIBRARY_PATH} ${OPENSSL_CRYPTO_LIBRARY_PATH}") else() message(FATAL_ERROR "Could not find OpenSSL libraries") endif() endif() if (ENABLE_CAPNP_SUPPORT) add_definitions(-DENABLE_CAPNP) find_library(CAPNP_LIBRARY_PATH NAMES capnp PATHS "${CAPNP_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) find_library(CAPNP_KJ_LIBRARY_PATH NAMES kj PATHS "${CAPNP_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (CAPNP_LIBRARY_PATH AND CAPNP_KJ_LIBRARY_PATH) message(STATUS "We found capnp and kj libraries: ${CAPNP_LIBRARY_PATH} ${CAPNP_KJ_LIBRARY_PATH}") else() message(FATAL_ERROR "Could not find capnp libraries") endif() include_directories("${CAPNP_CUSTOM_INSTALL_PATH}/include") target_link_libraries(simple_packet_capnp ${CAPNP_LIBRARY_PATH} ${CAPNP_KJ_LIBRARY_PATH}) # Link it with cap'n'p stuff target_link_libraries(fast_library simple_packet_capnp) endif() # With this flag we can control MongoDB build via console: cmake .. -DENABLE_MONGODB_SUPPORT=ON option(ENABLE_MONGODB_SUPPORT "Enable MongoDB support build" ON) if (ENABLE_MONGODB_SUPPORT) ### Find mongo-c find_path(MONGOC_INCLUDES_FOLDER NAMES libmongoc-1.0/mongoc.h PATHS "${MONGO_C_CUSTOM_INSTALL_PATH}/include" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) find_library(MONGOC_LIBRARY_PATH NAMES mongoc-1.0 PATHS "${MONGO_C_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) ### find bson find_path(BSON_INCLUDES_FOLDER NAMES libbson-1.0/bson.h PATHS "${MONGO_C_CUSTOM_INSTALL_PATH}/include" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) find_library(BSON_LIBRARY_PATH NAMES bson-1.0 PATHS "${MONGO_C_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) 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}) target_link_libraries(fastnetmon_logic ${MONGOC_LIBRARY_PATH} ${BSON_LIBRARY_PATH}) else() message(FATAL_ERROR "We can't find Mongo C library") endif() 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" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) # Try to find log4cpp library path find_library(LOG4CPP_LIBRARY_PATH NAMES log4cpp PATHS "${LOG4CPP_CUSTOM_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (LOG4CPP_INCLUDES_FOLDER AND LOG4CPP_LIBRARY_PATH) include_directories(${LOG4CPP_INCLUDES_FOLDER}) message(STATUS "We have found log4cpp: ${LOG4CPP_LIBRARY_PATH}") else() message(FATAL_ERROR "We can't find log4cpp. We can't build project") endif() target_link_libraries(fast_library ${OPENSSL_LIBRARY_PATH}) target_link_libraries(fast_library ${OPENSSL_CRYPTO_LIBRARY_PATH}) target_link_libraries(fastnetmon ${LOG4CPP_LIBRARY_PATH}) target_link_libraries(fastnetmon ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(fastnetmon fastnetmon_api) # We need it for boost::stacktrace # To address undefined reference to symbol 'dladdr@@GLIBC_2.2.5 target_link_libraries(fastnetmon ${CMAKE_DL_LIBS}) # Our libs target_link_libraries(fastnetmon patricia) target_link_libraries(fastnetmon fastnetmon_pcap_format) target_link_libraries(fastnetmon ipfix_rfc) target_link_libraries(fastnetmon_logic filter bgp_protocol bgp_protocol_flow_spec exabgp_action) target_link_libraries(fastnetmon_logic protobuf_traffic_format) target_link_libraries(fastnetmon_logic speed_counters) # Link to our functions target_link_libraries(fastnetmon fast_library) # link to our unified parser target_link_libraries(fastnetmon ${OPENSSL_LIBRARY_PATH}) target_link_libraries(fastnetmon ${OPENSSL_CRYPTO_LIBRARY_PATH}) if (ENABLE_GOBGP_SUPPORT) target_link_libraries(fastnetmon gobgp_action) endif() target_link_libraries(fastnetmon exabgp_action) if (ENABLE_AFPACKET_SUPPORT) target_link_libraries(fastnetmon afpacket_plugin) endif() if (ENABLE_AF_XDP_SUPPORT) target_link_libraries(fastnetmon xdp_plugin) endif() target_link_libraries(fastnetmon sflow_plugin netflow_plugin example_plugin) if (ENABLE_PCAP_SUPPORT) target_link_libraries(fastnetmon pcap_plugin) endif() target_link_libraries(fastnetmon fastnetmon_logic) if (ENABLE_NETMAP_SUPPORT) target_link_libraries(fastnetmon netmap_plugin) endif() # According to YunQiang Su debian-mips@lists.debian.org # Due to the limitation of gnu ld, -latomic should be put after library which calls it # I decided that keeping it here as very last dependency is pretty good option to guarantee it if (LINK_WITH_ATOMIC_LIBRARY) target_link_libraries(fastnetmon atomic) endif() # cmake .. -DBUILD_PLUGIN_RUNNER=ON if (BUILD_PLUGIN_RUNNER) add_executable(fastnetmon_plugin_runner plugin_runner.cpp) 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) if (ENABLE_NETMAP_SUPPORT) target_link_libraries(fastnetmon_plugin_runner netmap_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 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) if (ENABLE_NETMAP_SUPPORT) target_link_libraries(fastnetmon_pcap_reader netmap_plugin) 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}) find_library(GTEST_LIBRARY_PATH names "gtest" PATHS "${GTEST_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (GTEST_LIBRARY_PATH) message(STATUS "Found gTest library: ${GTEST_LIBRARY_PATH}") else() message(FATAL_ERROR "Can't find gTest library") endif() find_library(GTEST_MAIN_LIBRARY_PATH names "gtest_main" PATHS "${GTEST_INSTALL_PATH}/lib" ${DISABLE_DEFAULT_PATH_SEARCH_VAR}) if (GTEST_MAIN_LIBRARY_PATH) message(STATUS "Found gTest main library: ${GTEST_MAIN_LIBRARY_PATH}") else() message(FATAL_ERROR "Can't find gTest main library") endif() set(GOOGLE_TEST_INCLUDE_DIRS "${GTEST_INSTALL_PATH}/include") # Compiled Google Library include_directories(${GOOGLE_TEST_INCLUDE_DIRS}) target_link_libraries(fastnetmon_tests ${GTEST_LIBRARY_PATH} ${GTEST_MAIN_LIBRARY_PATH}) add_executable(traffic_structures_tests tests/traffic_structures_performance_tests.cpp) target_link_libraries(traffic_structures_tests ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${LOG4CPP_LIBRARY_PATH} fast_library) add_executable(traffic_structures_tests_real_traffic tests/traffic_structures_performance_tests_real_traffic.cpp) target_link_libraries(traffic_structures_tests_real_traffic ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${LOG4CPP_LIBRARY_PATH} fast_library) add_executable(speed_counters_performance_test tests/speed_counters_performance_test.cpp) target_link_libraries(speed_counters_performance_test ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} fast_library ${LOG4CPP_LIBRARY_PATH} speed_counters) add_executable(patricia_performance_tests tests/patricia_performance_tests.cpp) target_link_libraries(patricia_performance_tests patricia fast_library ${LOG4CPP_LIBRARY_PATH}) endif() # Check default values prepared by CMAKE for us message(STATUS "Install BINDIR path: ${CMAKE_INSTALL_BINDIR}") message(STATUS "Install SBINDIR path: ${CMAKE_INSTALL_SBINDIR}") message(STATUS "Install SYSCONFDIR path: ${CMAKE_INSTALL_SYSCONFDIR}") message(STATUS "Install MANDIR path: ${CMAKE_INSTALL_MANDIR}") # We use this flag on Debian upstream builds because we apparently need absolute paths here # But for Homebrew we need option to disable it and use relative paths # cmake .. -DSET_ABSOLUTE_INSTALL_PATH=OFF option(SET_ABSOLUTE_INSTALL_PATH "Enables use of absolute install paths" ON) if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly") set(CMAKE_INSTALL_BINDIR "bin") set(CMAKE_INSTALL_SBINDIR "bin") set(CMAKE_INSTALL_SYSCONFDIR "etc") elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if (SET_ABSOLUTE_INSTALL_PATH) set(CMAKE_INSTALL_BINDIR "/usr/bin") set(CMAKE_INSTALL_SBINDIR "/usr/sbin") set(CMAKE_INSTALL_SYSCONFDIR "/etc") set(CMAKE_INSTALL_MANDIR "/usr/share/man") endif() elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") message(STATUS "We run on Apple platform") else() message(STATUS "We run on platform ${CMAKE_SYSTEM_NAME} and we do not touch install paths") # Do not touch these variables and use default values endif() install(TARGETS fastnetmon DESTINATION "${CMAKE_INSTALL_SBINDIR}") install(TARGETS fastnetmon_client DESTINATION "${CMAKE_INSTALL_BINDIR}") install(TARGETS fastnetmon_api_client DESTINATION "${CMAKE_INSTALL_BINDIR}") install(FILES fastnetmon.conf DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}") # Install blank files for networks list and whitelist install(FILES networks_list DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}") install(FILES networks_whitelist DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}") # man pages install(FILES man/fastnetmon.8 DESTINATION ${CMAKE_INSTALL_MANDIR}/man8) install(FILES man/fastnetmon_client.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) if (SET_ABSOLUTE_INSTALL_PATH) # Unfortunately, we have no cross-platform option to install systemd units in current versions of cmake set(CMAKE_INSTALL_SYSTEMD_SERVICEDIR "/lib/systemd/system" CACHE PATH "Location for systemd service files") # Generate unit file if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") configure_file(fastnetmon.service.in "${CMAKE_CURRENT_BINARY_DIR}/fastnetmon.service" @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/fastnetmon.service" DESTINATION ${CMAKE_INSTALL_SYSTEMD_SERVICEDIR}) endif() else() # We have no relative standard path for installation, skip any actions endif() # For Debian packages build please use our logic from upstream: https://salsa.debian.org/debian/fastnetmon # Configure cpack package builder # Run it with: cd build; cpack -G DEB .. set(CPACK_PACKAGE_NAME "fastnetmon") set(CPACK_PACKAGE_VENDOR "fastnetmon.com") set(CPACK_PACKAGE_CONTACT "pavel.odintsov@gmail.com") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "FastNetMon - very fast DoS/DDoS detector with sFlow/Netflow/mirror support") set(CPACK_DEBIAN_PACKAGE_DEPENDS "") # set(CPACK_PACKAGE_INSTALL_DIRECTORY "CPack Component Example") if (NOT DO_NOT_USE_SYSTEM_LIBRARIES_FOR_BUILD) # Specify config for deb package # http://www.cmake.org/Wiki/CMake:CPackPackageGenerators#DEB_.28UNIX_only.29 # These dependencies are for Debian Bullseye set(CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-thread-dev, libboost-system-dev, libboost-regex-dev, libpcap-dev, libnuma-dev, liblog4cpp5-dev, libgrpc10, libgrpc++1, libcapnp-0.7.0, libmongoc-1.0-0, libbson-1.0-0, libboost-program-options1.74.0") endif() # This must always be last! include(CPack) # Fuzz test with AFL++ if (ENABLE_FUZZ_TEST) add_executable(parse_sflow_v5_packet_fuzz tests/fuzz/parse_sflow_v5_packet_fuzz.cpp) target_link_libraries(parse_sflow_v5_packet_fuzz sflow_plugin netflow_plugin example_plugin fastnetmon_logic ${LOG4CPP_LIBRARY_PATH}) add_executable(process_netflow_packet_v5_fuzz tests/fuzz/process_netflow_packet_v5_fuzz.cpp) target_link_libraries(process_netflow_packet_v5_fuzz sflow_plugin netflow_plugin example_plugin fastnetmon_logic ${LOG4CPP_LIBRARY_PATH}) endif() # Chaged interface socket to console input if (ENABLE_FUZZ_TEST_DESOCK) target_link_libraries(fastnetmon desock) endif() fastnetmon-1.2.8/src/Dockerfile000066400000000000000000000010771472727706000165020ustar00rootroot00000000000000FROM ubuntu:24.04 ENV CI=true ARG TARGETPLATFORM RUN apt-get update && apt-get upgrade -y && apt-get install -y wget RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ wget https://install.fastnetmon.com/installer -O installer; \ elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ wget https://install.fastnetmon.com/installer_arm64 -O installer; \ fi && \ chmod +x installer && \ ./installer -install_community_edition LABEL org.opencontainers.image.source=https://github.com/pavel-odintsov/fastnetmon CMD ["/opt/fastnetmon-community/app/bin/fastnetmon"] fastnetmon-1.2.8/src/a10_plugin/000077500000000000000000000000001472727706000164425ustar00rootroot00000000000000fastnetmon-1.2.8/src/a10_plugin/.gitignore000066400000000000000000000000421472727706000204260ustar00rootroot00000000000000*.pyc *.python *.egg *.egg-info/ fastnetmon-1.2.8/src/a10_plugin/README.md000066400000000000000000000101161472727706000177200ustar00rootroot00000000000000# A10 Networks Thunder TPS Appliance AXAPIv3 integration for FastNetMon ## Prerequisites: 1. A10 Thunder TPS with AXAPIv3. More information on AXAPIv3: https://www.a10networks.com/resources/glossary/axapi-custom-management. 2. Network topology is Asymmetric Reactive with BGP as the routing protocol. A10 Thunder TPS peers with the upstream router. 3. TPS contains base config under /fastnetmon/src/a10_plugin/configs/tps_base_config_vX.txt for base glid, zone-template, and ddos protection rate-interval, etc. ## Overview: 1. This script connect to A10 Thunder TPS Appliance via AXAPIv3 to create Protected Object. 2. The traffic is on-ramped by announcing a BGP route towards upstream router(s) upon FastNetMon ban detection. 3. The BGP route is withdrawn upon unban instruction from FastNetMon. 4. [Important] Please note that the script works in conjunction with the tps_base_config_v[xx].txt and tps_zone_config_v[xx].txt files. For example, the script assumes the 'bgp advertised' command is configured under 'ddos dst zone' to advertise the BGP route. Please consult with www.a10networks.com for the latest commands and configuration guides. 4.1 As a matter of reference, the tps_base_config and tps_zone_config configuration files were provided in .txt format under configs/ folder as well as in JSon format under json_configs/ folder. The assumption is they were pre-configured prior to FastNetMon ban/unban actions. 5. Log of the script is keep under /var/log/fastnetmon-notify.log. ## Configuration Steps: 1. If this is a brand new TPS with no prior 'ddos dst zone' config, do a quick dummy zone config and remove it: ``` TH3030S-1(config)#ddos dst zone dummy TH3030S-1(config-ddos zone)#exit TH3030S-1(config)#no ddos dst zone dummy TH3030S-1(config)#end TH3030S-1# ``` 2. Configure the fastnetmon_a10_xx.py script as the executed script under /etc/fastnetmon.conf, i.e. notify_script_path=/fastnetmon_a10_v0.3.py. 3. Please note that we have various versions of ban actions depending on your topology, such as integration of aGalaxy. 4. Alternatively place all files in a directory that is reachable by FastNetMon and indicate it as the executed script in /etc/fastnetmon.conf. 5. Make sure both Python scripts are executable, i.e. "chmod +x a10.py fastnetmon_a10_v0.3.py" ## Please modify the following in the fastnetmon_a10_v[xx].py script 1. A10 Thunder TPS mitigator IP. 2. Username and password for your A10 Device. Please follow your own password vault or other security schema. Author: Eric Chou ericc@a10networks.com, Rich Groves rgroves@a10networks.com Feedback and Feature Requests are Appreciated and Welcomed. Example Usage: - Ban action: ``` a10-ubuntu3:~/fastnetmon/src/a10_plugin$ sudo python fastnetmon_a10_v0.3.py "10.10.10.10" "outgoing" "111111" "ban" TH4435-1#show ddos dst zone all-entries Legend (Rate/Limit): 'U'nlimited, 'E'xceeded, '-' Not applicable Legend (State) : 'W'hitelisted, 'B'lacklisted, 'P'ermitted, black'H'oled, 'I'dle, 'L'earning, 'M'onitoring, '-' Regular mode Zone Name / Zone Service Info | [State]| Curr Conn| Conn Rate| Pkt Rate | kBit Rate|Frag Pkt R|Sources # |Age |LockU | | Limit| Limit| Limit| Limit| Limit| Limit|#min| Time ----------------------------------------------------------------------------------------------------------------------------------- 10.10.10.10_zone [M] U U U U U 1S 0 - U U U U U Displayed Entries: 1 Displayed Services: 0 TH4435#sh ip bgp neighbors advertised-routes ``` - Unban action: a10-ubuntu3:~/fastnetmon/src/a10_plugin$ sudo python fastnetmon_a10_v0.3.py "10.10.10.10" "outgoing" "111111" "unban" ``` TH4435-1#sh ip bgp neighbors advertised-routes TH4435-1# ``` ## Notes 1. In a10.py, SSL ssl._create_unverified_context() was used. Please see PEP476 for details. fastnetmon-1.2.8/src/a10_plugin/a10.py000077500000000000000000000030701472727706000174000ustar00rootroot00000000000000 # # v0.2 # ericc@a10networks.com # import json, urllib2, ssl def axapi_auth(host, username, password): base_uri = 'https://'+host auth_payload = {"credentials": {"username": username, "password": password}} r = axapi_action(base_uri + '/axapi/v3/auth', payload=auth_payload) signature = json.loads(r)['authresponse']['signature'] return base_uri, signature def axapi_action(uri, payload='', signature='', method='POST'): # PEP476 2.7.9+ / 3.4.3+ cert check new_context = ssl._create_unverified_context() try: if method == 'POST': req = urllib2.Request(uri) req.add_header('content-type', 'application/json') if signature: req.add_header('Authorization', 'A10 {0}'.format(signature)) response = urllib2.urlopen(req, json.dumps(payload), context=new_context) elif method == 'GET': req = urllib2.Request(uri) req.add_header('content-type', 'application/json') if signature: req.add_header('Authorization', 'A10 {0}'.format(signature)) response = urllib2.urlopen(req, context=new_context) elif method == 'DELETE': req = urllib2.Request(uri) req.add_header('content-type', 'application/json') req.get_method = lambda: 'DELETE' if signature: req.add_header('Authorization', 'A10 {0}'.format(signature)) response = urllib2.urlopen(req, context=new_context) return response.read() except Exception as e: raise fastnetmon-1.2.8/src/a10_plugin/change_log.txt000066400000000000000000000006441472727706000212750ustar00rootroot00000000000000Change Logs: [8/12/2016] - removed configs/dns_test_server.txt - added configs/tps_base_config_v1.txt and configs/tps_zone_config_v1.txt - modified README file to reflect the dependencies for items under configs/ folder. - created change_log.txt - modify json_configs/ddos_dst_zone.py to match json_configs/tps_zone_config_json_v1.txt - Took out BGP network advertisement, use 'bgp advertise' under dst zone instead fastnetmon-1.2.8/src/a10_plugin/configs/000077500000000000000000000000001472727706000200725ustar00rootroot00000000000000fastnetmon-1.2.8/src/a10_plugin/configs/README.md000066400000000000000000000022641472727706000213550ustar00rootroot00000000000000# A10 Networks Thunder TPS Appliance Configs ## Base Config v1 Functionality 1. Assumes TPS receives inbound traffic only (from the Internet to the protected service) 2. Rate Limiters (GLID) for 10Gbps, 1Gbps, and 100Mbps provided for use 3. Basic TCP and UDP templates provided (SYN-auth, UDP-auth, and low src port filter) 4. BGP configuration for auto mitigation announcements (ddos-advertise route map) 5. Base sFlow export configuration 6. All events logged in CEF format ## Basic Zone Config v1 Functionality 1. Filters L2, L3, L4 packet anomalies (consult A10 documentation for specifics) 2. Drops ICMPv4, ICMPv6, and all fragments 3. Performs TCP SYN Auth for TCP dest ports 21,22,25,53,80,110,143,443,587,993,995,5060,5061 4. Filters well-known UDP src ports 5. Performs UDP Auth for UDP dest port 53 6. Blocks all other traffic 7. Creates an "incident" in the TPS GUI when seeing any packets to these dest ports ## These are just examples. Current plug-in does not receive rate info from FNM but future revisions will Authors: Eric Chou ericc@a10networks.com, Rich Groves rgroves@a10networks.com Feedback and feature requests are appreciated and welcomed. fastnetmon-1.2.8/src/a10_plugin/configs/tps_base_config_v1.txt000066400000000000000000000027501472727706000243720ustar00rootroot00000000000000system anomaly log system attack log system ddos-attack log ! hostname A10TPS-Fastnetmon ! interface management ip address x.x.x.x x.x.x.x ip control-apps-use-mgmt-port ip default-gateway x.x.x.x enable ! interface ethernet 1 name Inbound enable ! interface ethernet 2 name Outbound ! ! glid 1 description "10gbps rate limiter" bit-rate-limit 10000000 ! glid 2 description "1gbps rate limiter" bit-rate-limit 1000000 ! glid 3 description "100mbps rate limiter" bit-rate-limit 100000 ! ddos protection enable ddos protection rate-interval 1sec ! ddos resource-tracking cpu enable ! ddos zone-template logging cef-logger log-format-cef enable-action-logging ! ddos zone-template tcp tcp-protect1 syn-authentication send-rst syn-authentication pass-action authenticate-src syn-authentication fail-action drop ! ddos zone-template udp udp-protect1 spoof-detect timeout 5 spoof-detect min-delay 2 spoof-detect pass-action authenticate-src spoof-detect fail-action drop known-resp-src-port action drop ! logging syslog information ! logging host x.x.x.x use-mgmt-port ! router bgp x bgp log-neighbor-changes bgp router-id x.x.x.x neighbor x.x.x.x remote-as x neighbor x.x.x.x description upstream neighbor x.x.x.x route-map ddos-advertise out ! route-map ddos-advertise permit 1 ! sflow setting max-header 128 sflow setting packet-sampling-rate 1000 ! sflow collector ip x.x.x.x 6343 use-mgmt-port ! sflow agent address x.x.x.x ! sflow sampling ethernet 1 ! end fastnetmon-1.2.8/src/a10_plugin/configs/tps_zone_config_v1.txt000066400000000000000000000063621472727706000244360ustar00rootroot00000000000000ddos dst zone xxxxxxx ip x.x.x.x operational-mode monitor bgp advertised zone-template logging cef-logger log enable periodic ip-proto tcp drop-frag-pkt ip-proto udp drop-frag-pkt ip-proto icmp-v4 deny detection-enable ip-proto icmp-v6 deny detection-enable port 20 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 21 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 22 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 25 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 53 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 53 udp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template udp udp-protect1 port 80 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 110 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 143 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 443 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 587 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 993 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 995 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 5060 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port 5061 tcp detection-enable level 0 zone-escalation-score 10 indicator pkt-rate score 20 zone-threshold 1 level 1 zone-template tcp tcp-protect1 port other tcp detection-enable deny port other udp detection-enable deny fastnetmon-1.2.8/src/a10_plugin/fastnetmon_a10_v0.3.py000077500000000000000000000047131472727706000224110ustar00rootroot00000000000000#!/usr/bin/python # # Eric Chou (ericc@a10networks.com) # import sys from sys import stdin import optparse import logging, json from a10 import axapi_auth, axapi_action from json_configs.logoff import logoff_path from json_configs.write_memory import write_mem_path from json_configs.ddos_dst_zone import ddos_dst_zone_path, ddos_dst_zone LOG_FILE = "/var/log/fastnetmon-notify.log" 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)) # A10 Mitigator Information mitigator_ip = "192.168.199.150" zone_name = client_ip_as_string + "_zone" ip_addr = client_ip_as_string mitigator_base_url, signature = axapi_auth(mitigator_ip, "admin", "a10") if action == "unban": try: r = axapi_action(mitigator_base_url+ddos_dst_zone_path+zone_name, method="DELETE", signature=signature) except Exception as e: logger.info("Zone not removed in unban, may not exist. Result: " + str(e)) # Commit config axapi_action(mitigator_base_url+write_mem_path, signature=signature) # Logoff axapi_action(mitigator_base_url+logoff_path, signature=signature) sys.exit(0) elif action == "ban": r = axapi_action(mitigator_base_url+ddos_dst_zone_path, method='GET', signature=signature) try: if zone_name in [i['zone-name'] for i in json.loads(r)['zone-list']]: r = axapi_action(mitigator_base_url+ddos_dst_zone_path+zone_name, method="DELETE", signature=signature) logger.info(str(r)) except Exception as e: logger.info("No Zone detected or something went wrong. Erorr: " + str(e)) # A10 Mitigation On Ramp zone_name = client_ip_as_string + "_zone" ip_addr = client_ip_as_string returned_body = ddos_dst_zone(zone_name, ip_addr) try: r = axapi_action(mitigator_base_url+ddos_dst_zone_path, signature=signature, payload=returned_body) except Exception as e: logger.info("zone not created: " + str(e)) # Commit changes axapi_action(mitigator_base_url+write_mem_path, signature=signature) # Log off axapi_action(mitigator_base_url+logoff_path, signature=signature) sys.exit(0) else: sys.exit(0) fastnetmon-1.2.8/src/a10_plugin/json_configs/000077500000000000000000000000001472727706000211235ustar00rootroot00000000000000fastnetmon-1.2.8/src/a10_plugin/json_configs/__init__.py000066400000000000000000000000001472727706000232220ustar00rootroot00000000000000fastnetmon-1.2.8/src/a10_plugin/json_configs/bgp.py000066400000000000000000000005271472727706000222510ustar00rootroot00000000000000bgp_advertisement_path = '/axapi/v3/router/bgp/' def bgp_advertisement(ip_addr): route_advertisement = { "bgp": { "network": { "ip-cidr-list": [ { "network-ipv4-cidr":ip_addr+"/32", } ] }, } } return route_advertisement fastnetmon-1.2.8/src/a10_plugin/json_configs/ddos_dst_zone.py000066400000000000000000000252441472727706000243420ustar00rootroot00000000000000 ddos_dst_zone_path = '/axapi/v3/ddos/dst/zone/' def ddos_dst_zone(zone_name, ip_addr): ddos_dst_zone_payload = { "zone-list": [ { "zone-name":zone_name, "ip": [ { "ip-addr": ip_addr, } ], "operational-mode":"monitor", "advertised-enable":1, "zone-template": { "logging":"cef-logger" }, "log-enable":1, "log-periodic":1, "ip-proto": { "proto-tcp-udp-list": [ { "protocol":"tcp", "drop-frag-pkt":1, }, { "protocol":"udp", "drop-frag-pkt":1, } ], "proto-name-list": [ { "protocol":"icmp-v4", "deny":1, "detection-enable":1, }, { "protocol":"icmp-v6", "deny":1, "detection-enable":1, } ] }, "port": { "zone-service-list": [ { "port-num":20, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":21, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":22, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":25, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":53, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":53, "protocol":"udp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "udp":"udp-protect1" }, } ] }, { "port-num":80, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":110, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":143, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":443, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":587, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":993, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":995, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":5060, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":5061, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] } ], "zone-service-other-list": [ { "port-other":"other", "protocol":"tcp", "detection-enable":1, "deny":1, }, { "port-other":"other", "protocol":"udp", "detection-enable":1, "deny":1, } ] } } ] } return ddos_dst_zone_payload fastnetmon-1.2.8/src/a10_plugin/json_configs/ddos_dst_zone_backup.py000066400000000000000000000021171472727706000256610ustar00rootroot00000000000000 ddos_dst_zone_path = '/axapi/v3/ddos/dst/zone/' def ddos_dst_zone(zone_name, ip_addr): port_num = 53 port_protocol = 'udp' ddos_dst_zone_payload = { "zone-list": [ { "zone-name":zone_name, "ip": [ { "ip-addr":ip_addr } ], "operational-mode":"monitor", "port": { "zone-service-list": [ { "port-num":port_num, "protocol":port_protocol, "level-list": [ { "level-num":"0", "zone-escalation-score":1, "indicator-list": [ { "type":"pkt-rate", "score":50, "zone-threshold-num":1, } ], }, { "level-num":"1", } ], } ], }, } ] } return ddos_dst_zone_payload fastnetmon-1.2.8/src/a10_plugin/json_configs/logoff.py000066400000000000000000000000431472727706000227460ustar00rootroot00000000000000 logoff_path = '/axapi/v3/logoff' fastnetmon-1.2.8/src/a10_plugin/json_configs/tps_base_config_json_v1.txt000066400000000000000000000101761472727706000264550ustar00rootroot00000000000000a10-url:/axapi/v3/admin { "admin-list": [ { "user":"admin", "password": { "encrypted-in-module":"sCyT4priW1OZSg3m1RiAf0bOyZ0Odnf1rQRp+BHohemGp1YhW+V1NjwQjLjV2wDn", } } ] } a10-url:/axapi/v3/multi-config { "multi-config": { "enable":1, } } a10-url:/axapi/v3/monitor { "monitor": { "buffer-usage":91750, } } a10-url:/axapi/v3/system { "system": { "anomaly-log":1, "attack":1, "attack-log":1, "ddos-attack":1, "ddos-log":1, } } a10-url:/axapi/v3/hostname { "hostname": { "value":"tps-fastnetmon", } } a10-url:/axapi/v3/interface/management { "management": { "ip": { "ipv4-address”:”x.x.x.x", "ipv4-netmask”:”x.x.x.x", "control-apps-use-mgmt-port":1, "default-gateway”:”x.x.x.x" }, "action":"enable", } } a10-url:/axapi/v3/interface/ethernet { "ethernet-list": [ { "ifnum":1, "name":"Inbound", "action":"enable", }, { "ifnum":2, "name":"Outbound", } ] } a10-url:/axapi/v3/glid { "glid-list": [ { "name":"1", "description":"10gbps rate limiter", "bit-rate-limit":10000000, }, { "name":"2", "description":"1gbps rate limiter", "bit-rate-limit":1000000, }, { "name":"3", "description":"100mbps rate limiter", "bit-rate-limit":100000, } ] } a10-url:/axapi/v3/ddos/protection { "protection": { "toggle":"enable", "rate-interval":"1sec", } } a10-url:/axapi/v3/ddos/resource-tracking/cpu { "cpu": { "enable":1, } } a10-url:/axapi/v3/ddos/zone-template/logging { "logging-list": [ { "logging-tmpl-name":"cef-logger", "log-format-cef":1, "enable-action-logging":1, } ] } a10-url:/axapi/v3/ddos/zone-template/tcp { "tcp-list": [ { "name":"tcp-protect1", "syn-authentication": { "syn-auth-type":"send-rst", "syn-auth-pass-action":"authenticate-src", "syn-auth-fail-action":"drop" }, } ] } a10-url:/axapi/v3/ddos/zone-template/udp { "udp-list": [ { "name":"udp-protect1", "spoof-detect-retry-timeout":5, "spoof-detect-min-delay":2, "spoof-detect-pass-action":"authenticate-src", "spoof-detect-fail-action":"drop", "known-resp-src-port-cfg": { "known-resp-src-port":1, "known-resp-src-port-action":"drop" }, } ] } a10-url:/axapi/v3/ddos/src/default { "default-list": [ { "default-address-type":"ip", }, { "default-address-type":"ipv6", } ] } a10-url:/axapi/v3/ddos/dst/default { "default-list": [ { "default-address-type":"ip", }, { "default-address-type":"ipv6", } ] } a10-url:/axapi/v3/logging/syslog { "syslog": { "syslog-levelname":"information", } } a10-url:/axapi/v3/logging/host/ipv4addr { "ipv4addr-list": [ { "host-ipv4”:”x.x.x.x", "use-mgmt-port":1, "tcp":0, } ] } a10-url:/axapi/v3/router/bgp { "bgp-list": [ { "as-number”:x, "bgp": { "log-neighbor-changes":1, "router-id”:”x.x.x.x" }, "neighbor": { "ipv4-neighbor-list": [ { "neighbor-ipv4”:”x.x.x.x", "nbr-remote-as":1, "description":"upstream", "neighbor-route-map-lists": [ { "nbr-route-map":"ddos-advertise", "nbr-rmap-direction":"out" } ], } ] } } ] } a10-url:/axapi/v3/route-map { "route-map-list": [ { "tag":"ddos-advertise", "action":"permit", "sequence":1, } ] } a10-url:/axapi/v3/sflow/setting { "setting": { "max-header":128, "packet-sampling-rate":1000, } } a10-url:/axapi/v3/sflow/collector/ip { "ip-list": [ { "addr”:”x.x.x.x", "port":6343, "use-mgmt-port":1, } ] } a10-url:/axapi/v3/sflow/agent/address { "address": { "ip”:”x.x.x.x", } } a10-url:/axapi/v3/sflow/sampling { "sampling": { "eth-list": [ { "eth-start":1, "eth-end":1 } ], } } fastnetmon-1.2.8/src/a10_plugin/json_configs/tps_zone_config_json_v1.txt000066400000000000000000000250551472727706000265200ustar00rootroot00000000000000a10-url:/axapi/v3/ddos/dst/zone { "zone-list": [ { "zone-name”:"xxxx", "ip": [ { "ip-addr”:”x.x.x.x" } ], "operational-mode":"monitor", "advertised-enable":1, "zone-template": { "logging":"cef-logger" }, "log-enable":1, "log-periodic":1, "ip-proto": { "proto-tcp-udp-list": [ { "protocol":"tcp", "drop-frag-pkt":1, }, { "protocol":"udp", "drop-frag-pkt":1, } ], "proto-name-list": [ { "protocol":"icmp-v4", "deny":1, "detection-enable":1, }, { "protocol":"icmp-v6", "deny":1, "detection-enable":1, } ] }, "port": { "zone-service-list": [ { "port-num":20, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":21, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":22, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":25, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":53, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":53, "protocol":"udp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "udp":"udp-protect1" }, } ] }, { "port-num":80, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":110, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":143, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":443, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":587, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":993, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":995, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":5060, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] }, { "port-num":5061, "protocol":"tcp", "detection-enable":1, "level-list": [ { "level-num":"0", "zone-escalation-score":10, "indicator-list": [ { "type":"pkt-rate", "score":20, "zone-threshold-num":1, } ] }, { "level-num":"1", "zone-template": { "tcp":"tcp-protect1" }, } ] } ], "zone-service-other-list": [ { "port-other":"other", "protocol":"tcp", "detection-enable":1, "deny":1, }, { "port-other":"other", "protocol":"udp", "detection-enable":1, "deny":1, } ] } } ] } fastnetmon-1.2.8/src/a10_plugin/json_configs/write_memory.py000066400000000000000000000000521472727706000242140ustar00rootroot00000000000000write_mem_path = '/axapi/v3/write/memory' fastnetmon-1.2.8/src/a10_plugin/tests/000077500000000000000000000000001472727706000176045ustar00rootroot00000000000000fastnetmon-1.2.8/src/a10_plugin/tests/README.md000066400000000000000000000022211472727706000210600ustar00rootroot00000000000000## Sample Test Output ``` echou@a10-ubuntu3:~/fastnetmon/src/a10_plugin/tests$ python helperTests.py Testing GET { "version": { "oper" : { "hw-platform":"TH4435 TPS", "copyright":"Copyright 2007-2014 by A10 Networks, Inc.", "sw-version":"3.2.1 build 175 (May-17-2016,16:57)", "plat-features":"", "boot-from":"HD_PRIMARY", "serial-number":"", "current-time":"Jul-27-2016, 09:46", "up-time":"70 days, 22 hours, 44 minutes" }, "a10-url":"/axapi/v3/version/oper" } } Testing POST { "hostname": { "value":"TH4435", "uuid":"", "a10-url":"/axapi/v3/hostname" } } .Testing axapi_auth ('base url: ', 'https://192.168.199.152', 'Signature: ', u'0855fef4da06d7beb89b27e7d2d042') . ---------------------------------------------------------------------- Ran 2 tests in 0.092s OK echou@a10-ubuntu3:~/fastnetmon/src/a10_plugin/tests$ ``` fastnetmon-1.2.8/src/a10_plugin/tests/__init__.py000066400000000000000000000000001472727706000217030ustar00rootroot00000000000000fastnetmon-1.2.8/src/a10_plugin/tests/helperTests.py000066400000000000000000000025141472727706000224620ustar00rootroot00000000000000import unittest,sys sys.path.append('../') from a10 import axapi_auth, axapi_action a10_tps = "192.168.199.152" username = "admin" password = "a10" hostname = "TH4435" class Test_Auth(unittest.TestCase): def testAssertTrue(self): print("Testing axapi_auth") try: mitigator_base_url, signature = axapi_auth(a10_tps, username, password) print("base url: ", mitigator_base_url, "Signature: ", signature) axapi_action(mitigator_base_url+"/axapi/v3/logoff") except Exception as e: self.fail("Not authenticated") class Test_API_Actions(unittest.TestCase): def testAssertTrue(self): try: print("Testing GET") mitigator_base_url, signature = axapi_auth(a10_tps, username, password) r = axapi_action(mitigator_base_url+"/axapi/v3/version/oper", method='GET', signature=signature) print(str(r)) print("Testing POST") hostname_payload = {"hostname": {"value": hostname}} r = axapi_action(mitigator_base_url+"/axapi/v3/hostname", payload=hostname_payload, signature=signature) print(str(r)) axapi_action(mitigator_base_url+"/axapi/v3/logoff") except Exception as e: self.fail("Failed") if __name__ == "__main__": unittest.main() fastnetmon-1.2.8/src/abstract_subnet_counters.hpp000066400000000000000000000235111472727706000223230ustar00rootroot00000000000000#pragma once #include #include #include "speed_counters.hpp" // // Even latest Debian Sid (March 2023) uses Boost 1.74 which does not behave well with very fresh compilers and triggers this error: // https://github.com/pavel-odintsov/fastnetmon/issues/970 // This bug was fixed in fresh Boost versions: https://github.com/boostorg/serialization/issues/219 and we apply workaround only for 1.74 // #include #if BOOST_VERSION / 100000 == 1 && BOOST_VERSION / 100 % 1000 == 74 #include #endif #include // Class for abstract per key counters template > class abstract_subnet_counters_t { public: UM counter_map; std::mutex counter_map_mutex; UM average_speed_map; // By using single map for speed and data we can accomplish improvement from 3-4 seconds for 14m hosts to 2-3 seconds template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(counter_map); ar& BOOST_SERIALIZATION_NVP(average_speed_map); } // Increments outgoing counters for specified key void increment_outgoing_counters_for_key(const T& key, const simple_packet_t& current_packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes) { std::lock_guard lock_guard(counter_map_mutex); Counter& counters = counter_map[key]; increment_outgoing_counters(counters, current_packet, sampled_number_of_packets, sampled_number_of_bytes); } // Increments outgoing counters for specified key using multimatch array with indexes of matched thresholds template void increment_outgoing_counters_for_key(const T& key, const std::array& matched_indexes, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes) { std::lock_guard lock_guard(counter_map_mutex); Counter& counters = counter_map[key]; extern time_t current_inaccurate_time; // Update last update time counters.last_update_time = current_inaccurate_time; for (std::size_t current_index = 0; current_index < counters.flexible_counters.size(); current_index++) { // Increment only counters which are relevant to specific flexible threshold if (matched_indexes[current_index]) { counters.flexible_counters[current_index].out_packets += sampled_number_of_packets; counters.flexible_counters[current_index].out_bytes += sampled_number_of_bytes; } } } // Increments incoming counters for specified key void increment_incoming_counters_for_key(const T& key, const simple_packet_t& current_packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes) { std::lock_guard lock_guard(counter_map_mutex); Counter& counters = counter_map[key]; increment_incoming_counters(counters, current_packet, sampled_number_of_packets, sampled_number_of_bytes); } // Increments incoming counters for specified key using multi match array with indexes of matched thresholds template void increment_incoming_counters_for_key(const T& key, const std::array& matched_indexes, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes) { std::lock_guard lock_guard(counter_map_mutex); Counter& counters = counter_map[key]; extern time_t current_inaccurate_time; // Update last update time counters.last_update_time = current_inaccurate_time; for (std::size_t current_index = 0; current_index < counters.flexible_counters.size(); current_index++) { // Increment only counters which are relevant to specific flexible threshold if (matched_indexes[current_index]) { counters.flexible_counters[current_index].in_packets += sampled_number_of_packets; counters.flexible_counters[current_index].in_bytes += sampled_number_of_bytes; } } } // Retrieves all elements void get_all_average_speed_elements(UM& copy_of_average_speed_map) { std::lock_guard lock_guard(counter_map_mutex); copy_of_average_speed_map = this->average_speed_map; } uint64_t purge_old_data(unsigned int automatic_data_cleanup_threshold) { std::lock_guard lock_guard(this->counter_map_mutex); std::vector keys_to_remove; time_t current_time = 0; time(¤t_time); for (auto itr = this->counter_map.begin(); itr != this->counter_map.end(); ++itr) { if ((int64_t)itr->second.last_update_time < int64_t((int64_t)current_time - (int64_t)automatic_data_cleanup_threshold)) { keys_to_remove.push_back(itr->first); } } for (const auto& key : keys_to_remove) { counter_map.erase(key); average_speed_map.erase(key); } // Report number of removed records return keys_to_remove.size(); } void recalculate_speed(double speed_calc_period, double average_calculation_time, std::function speed_check_callback = nullptr, std::function new_speed_calc_callback = nullptr) { // http://en.wikipedia.org/wiki/Moving_average#Application_to_measuring_computer_performance double exp_power_subnet = -speed_calc_period / average_calculation_time; double exp_value_subnet = exp(exp_power_subnet); std::lock_guard lock_guard(this->counter_map_mutex); for (auto itr = this->counter_map.begin(); itr != this->counter_map.end(); ++itr) { // Create const reference to key to easily reference to it in code const T& current_key = itr->first; // Create normal reference Counter& traffic_counters = itr->second; // Create element for instant speed Counter new_speed_element; build_speed_counters_from_packet_counters(new_speed_element, traffic_counters, speed_calc_period); // We can call callback function to populate more data here if (new_speed_calc_callback != nullptr) { new_speed_calc_callback(current_key, new_speed_element, speed_calc_period); } // Get reference to average speed element Counter& current_average_speed_element = average_speed_map[current_key]; build_average_speed_counters_from_speed_counters(current_average_speed_element, new_speed_element, exp_value_subnet); traffic_counters.zeroify(); // Check thresholds if (speed_check_callback != nullptr) { speed_check_callback(current_key, current_average_speed_element); } } } // Returns all non zero average speed elements void get_all_non_zero_average_speed_elements_as_pairs(std::vector>& all_elements) { std::lock_guard lock_guard(this->counter_map_mutex); for (auto itr = this->average_speed_map.begin(); itr != this->average_speed_map.end(); ++itr) { if (itr->second.is_zero()) { continue; } all_elements.push_back(std::make_pair(itr->first, itr->second)); } } void get_sorted_average_speed(std::vector>& vector_for_sort, const attack_detection_threshold_type_t& sorter_type, const attack_detection_direction_type_t& sort_direction) { std::lock_guard lock_guard(this->counter_map_mutex); vector_for_sort.reserve(average_speed_map.size()); std::copy(average_speed_map.begin(), average_speed_map.end(), std::back_inserter(vector_for_sort)); std::sort(vector_for_sort.begin(), vector_for_sort.end(), TrafficComparatorClass>(sort_direction, sorter_type)); } // Retrieves average speed for specified key with all locks bool get_average_speed(const T& key, Counter& average_speed_element) { std::lock_guard lock_guard(this->counter_map_mutex); auto average_speed_itr = this->average_speed_map.find(key); if (average_speed_itr == this->average_speed_map.end()) { return false; } average_speed_element = average_speed_itr->second; return true; } // Please create vector_for_sort this way on callers side: top_four(4); void get_top_k_average_speed(std::vector>& vector_for_sort, const attack_detection_threshold_type_t& sorter_type, const attack_detection_direction_type_t& sort_direction) { std::lock_guard lock_guard(this->counter_map_mutex); std::partial_sort_copy(average_speed_map.begin(), average_speed_map.end(), vector_for_sort.begin(), vector_for_sort.end(), TrafficComparatorClass>(sort_direction, sorter_type)); } }; fastnetmon-1.2.8/src/actions/000077500000000000000000000000001472727706000161435ustar00rootroot00000000000000fastnetmon-1.2.8/src/actions/exabgp_action.cpp000066400000000000000000000044201472727706000214520ustar00rootroot00000000000000#include "exabgp_action.hpp" #include #include "../fast_library.hpp" #include "../all_logcpp_libraries.hpp" extern bool exabgp_enabled; extern std::string exabgp_community; extern std::string exabgp_community_subnet; extern std::string exabgp_community_host; extern std::string exabgp_command_pipe; extern std::string exabgp_next_hop; extern bool exabgp_announce_host; extern bool exabgp_announce_whole_subnet; extern log4cpp::Category& logger; // 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); } void exabgp_ban_manage(const std::string& action, const std::string& ip_as_string, const subnet_cidr_mask_t& customer_network) { // We will announce whole subnet here if (exabgp_announce_whole_subnet) { std::string subnet_as_string_with_mask = convert_subnet_to_string(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); } } fastnetmon-1.2.8/src/actions/exabgp_action.hpp000066400000000000000000000002661472727706000214630ustar00rootroot00000000000000#include "../fastnetmon_types.hpp" #include void exabgp_ban_manage(const std::string& action, const std::string& ip_as_string, const subnet_cidr_mask_t& customer_network); fastnetmon-1.2.8/src/actions/gobgp_action.cpp000066400000000000000000000326141472727706000213100ustar00rootroot00000000000000#include "gobgp_action.hpp" #include "../fastnetmon_actions.hpp" #include #include #include "../bgp_protocol.hpp" #include "../gobgp_client/gobgp_client.hpp" #include "../fastnetmon_configuration_scheme.hpp" extern fastnetmon_configuration_t fastnetmon_global_configuration; void gobgp_action_init() { logger << log4cpp::Priority::INFO << "GoBGP action module loaded"; if (configuration_map.count("gobgp_next_hop")) { fastnetmon_global_configuration.gobgp_next_hop = configuration_map["gobgp_next_hop"]; } if (configuration_map.count("gobgp_next_hop_ipv6")) { fastnetmon_global_configuration.gobgp_next_hop_ipv6 = configuration_map["gobgp_next_hop_ipv6"]; } if (configuration_map.count("gobgp_next_hop_host_ipv6")) { fastnetmon_global_configuration.gobgp_next_hop_host_ipv6 = configuration_map["gobgp_next_hop_host_ipv6"]; } if (configuration_map.count("gobgp_next_hop_subnet_ipv6")) { fastnetmon_global_configuration.gobgp_next_hop_subnet_ipv6 = configuration_map["gobgp_next_hop_subnet_ipv6"]; } if (configuration_map.count("gobgp_announce_host")) { fastnetmon_global_configuration.gobgp_announce_host = configuration_map["gobgp_announce_host"] == "on"; } if (configuration_map.count("gobgp_announce_whole_subnet")) { fastnetmon_global_configuration.gobgp_announce_whole_subnet = configuration_map["gobgp_announce_whole_subnet"] == "on"; } if (configuration_map.count("gobgp_announce_host_ipv6")) { fastnetmon_global_configuration.gobgp_announce_host_ipv6 = configuration_map["gobgp_announce_host_ipv6"] == "on"; } if (configuration_map.count("gobgp_announce_whole_subnet_ipv6")) { fastnetmon_global_configuration.gobgp_announce_whole_subnet_ipv6 = configuration_map["gobgp_announce_whole_subnet_ipv6"] == "on"; } if (configuration_map.count("gobgp_community_host")) { fastnetmon_global_configuration.gobgp_community_host = configuration_map["gobgp_community_host"]; } if (configuration_map.count("gobgp_community_subnet")) { fastnetmon_global_configuration.gobgp_community_subnet = configuration_map["gobgp_community_subnet"]; } if (configuration_map.count("gobgp_community_host_ipv6")) { fastnetmon_global_configuration.gobgp_community_host_ipv6 = configuration_map["gobgp_community_host_ipv6"]; } if (configuration_map.count("gobgp_community_subnet_ipv6")) { fastnetmon_global_configuration.gobgp_community_subnet_ipv6 = configuration_map["gobgp_community_subnet_ipv6"]; } } void gobgp_action_shutdown() { } void gobgp_ban_manage_ipv6(GrpcClient& gobgp_client, const subnet_ipv6_cidr_mask_t& client_ipv6, bool is_withdrawal, const attack_details_t& current_attack) { // TODO: that's very weird approach to use subnet_ipv6_cidr_mask_t for storing next hop which is HOST address // We need to rework all structures in stack of BGP logic to switch it to plain in6_addr subnet_ipv6_cidr_mask_t ipv6_next_hop_legacy{}; ipv6_next_hop_legacy.cidr_prefix_length = 128; //-V1048 bool parsed_next_hop_result = read_ipv6_host_from_string(fastnetmon_global_configuration.gobgp_next_hop_ipv6, ipv6_next_hop_legacy.subnet_address); if (!parsed_next_hop_result) { logger << log4cpp::Priority::ERROR << "Can't parse specified IPv6 next hop to IPv6 address: " << fastnetmon_global_configuration.gobgp_next_hop_ipv6; return; } // Starting July 2024, 1.2.8 we have capability to specify different next hops for host and subnet subnet_ipv6_cidr_mask_t gobgp_next_hop_host_ipv6{}; gobgp_next_hop_host_ipv6.cidr_prefix_length = 128; //-V1048 if (fastnetmon_global_configuration.gobgp_next_hop_host_ipv6 != "") { if (!read_ipv6_host_from_string(fastnetmon_global_configuration.gobgp_next_hop_host_ipv6, gobgp_next_hop_host_ipv6.subnet_address)) { logger << log4cpp::Priority::ERROR << "Can't parse specified IPv6 next hop gobgp_next_hop_host_ipv6 as IPv6 address: " << fastnetmon_global_configuration.gobgp_next_hop_host_ipv6; // We do not stop processing here. If we failed then let's keep it zero } } else { // That's fine. It's expected to be empty on new installations } // Starting July 2024, 1.2.8 we have capability to specify different next hops for host and subnet subnet_ipv6_cidr_mask_t gobgp_next_hop_subnet_ipv6{}; gobgp_next_hop_subnet_ipv6.cidr_prefix_length = 128; //-V1048 if (fastnetmon_global_configuration.gobgp_next_hop_subnet_ipv6 != "") { if (!read_ipv6_host_from_string(fastnetmon_global_configuration.gobgp_next_hop_subnet_ipv6, gobgp_next_hop_subnet_ipv6.subnet_address)) { logger << log4cpp::Priority::ERROR << "Can't parse specified IPv6 next hop gobgp_next_hop_subnet_ipv6 as IPv6 address: " << fastnetmon_global_configuration.gobgp_next_hop_subnet_ipv6; // We do not stop processing here. If we failed then let's keep it zero } } else { // That's fine. It's expected to be empty on new installations } // For backward compatibility with old deployments which still use value gobgp_next_hop_ipv6 we check new value and if it's zero use old one if (is_zero_ipv6_address(gobgp_next_hop_host_ipv6.subnet_address)) { logger << log4cpp::Priority::INFO << "gobgp_next_hop_host_ipv6 is zero, will use global gobgp_next_hop_ipv6: " << fastnetmon_global_configuration.gobgp_next_hop_ipv6; gobgp_next_hop_host_ipv6.subnet_address = ipv6_next_hop_legacy.subnet_address; } if (is_zero_ipv6_address(gobgp_next_hop_subnet_ipv6.subnet_address)) { logger << log4cpp::Priority::INFO << "gobgp_next_hop_subnet_ipv6 is zero, will use global gobgp_next_hop_ipv6: " << fastnetmon_global_configuration.gobgp_next_hop_ipv6; gobgp_next_hop_subnet_ipv6.subnet_address = ipv6_next_hop_legacy.subnet_address; } if (fastnetmon_global_configuration.gobgp_announce_host_ipv6) { IPv6UnicastAnnounce unicast_ipv6_announce; std::vector host_ipv6_communities; // This one is an old configuration option which can carry only single community host_ipv6_communities.push_back(fastnetmon_global_configuration.gobgp_community_host_ipv6); for (auto community_string : host_ipv6_communities) { bgp_community_attribute_element_t bgp_community_host; if (!read_bgp_community_from_string(community_string, bgp_community_host)) { logger << log4cpp::Priority::ERROR << "Could not decode BGP community for IPv6 host: " << community_string; // We may have multiple communities and other communities may be correct, skip only broken one continue; } unicast_ipv6_announce.add_community(bgp_community_host); } unicast_ipv6_announce.set_prefix(client_ipv6); unicast_ipv6_announce.set_next_hop(gobgp_next_hop_host_ipv6); gobgp_client.AnnounceUnicastPrefixLowLevelIPv6(unicast_ipv6_announce, is_withdrawal); } if (fastnetmon_global_configuration.gobgp_announce_whole_subnet_ipv6) { logger << log4cpp::Priority::ERROR << "Sorry but we do not support IPv6 per subnet announces"; } } void gobgp_ban_manage_ipv4(GrpcClient& gobgp_client, uint32_t client_ip, bool is_withdrawal, const attack_details_t& current_attack) { // Previously we used same next hop for both subnet and host uint32_t next_hop_as_integer_legacy = 0; if (!convert_ip_as_string_to_uint_safe(fastnetmon_global_configuration.gobgp_next_hop, next_hop_as_integer_legacy)) { logger << log4cpp::Priority::ERROR << "Could not decode next hop to numeric form: " << fastnetmon_global_configuration.gobgp_next_hop; return; } // Starting July 2024, 1.1.8 we have capability to specify different next hops for host and subnet uint32_t gobgp_next_hop_host_ipv4 = 0; uint32_t gobgp_next_hop_subnet_ipv4 = 0; // Read next hop for host if (fastnetmon_global_configuration.gobgp_next_hop_host_ipv4 != "") { if (!convert_ip_as_string_to_uint_safe(fastnetmon_global_configuration.gobgp_next_hop_host_ipv4, gobgp_next_hop_host_ipv4)) { logger << log4cpp::Priority::ERROR << "Could not decode next hop to numeric form for gobgp_next_hop_host_ipv4: " << fastnetmon_global_configuration.gobgp_next_hop_host_ipv4; // We do not stop processing here. If we failed then let's keep it zero } } else { // That's fine. It's expected to be empty on new installations } // Read next hop for subnet if (fastnetmon_global_configuration.gobgp_next_hop_subnet_ipv4 != "") { if (!convert_ip_as_string_to_uint_safe(fastnetmon_global_configuration.gobgp_next_hop_subnet_ipv4, gobgp_next_hop_subnet_ipv4)) { logger << log4cpp::Priority::ERROR << "Could not decode next hop to numeric form for gobgp_next_hop_subnet_ipv4: " << fastnetmon_global_configuration.gobgp_next_hop_subnet_ipv4; // We do not stop processing here. If we failed then let's keep it zero } } else { // That's fine. It's expected to be empty on new installations } // For backward compatibility with old deployments which still use value gobgp_next_hop we check new value and if it's zero use old one if (gobgp_next_hop_host_ipv4 == 0) { logger << log4cpp::Priority::INFO << "gobgp_next_hop_host_ipv4 is empty, will use global gobgp_next_hop: " << fastnetmon_global_configuration.gobgp_next_hop; gobgp_next_hop_host_ipv4 = next_hop_as_integer_legacy; } if (gobgp_next_hop_subnet_ipv4 == 0) { logger << log4cpp::Priority::INFO << "gobgp_next_hop_subnet_ipv4 is empty, will use global gobgp_next_hop: " << fastnetmon_global_configuration.gobgp_next_hop; gobgp_next_hop_subnet_ipv4 = next_hop_as_integer_legacy; } if (fastnetmon_global_configuration.gobgp_announce_whole_subnet) { IPv4UnicastAnnounce unicast_ipv4_announce; std::vector subnet_ipv4_communities; subnet_ipv4_communities.push_back(fastnetmon_global_configuration.gobgp_community_subnet); for (auto community_string : subnet_ipv4_communities) { bgp_community_attribute_element_t bgp_community_subnet; if (!read_bgp_community_from_string(community_string, bgp_community_subnet)) { logger << log4cpp::Priority::ERROR << "Could not decode BGP community for IPv4 subnet"; // We may have multiple communities and other communities may be correct, skip only broken one continue; } unicast_ipv4_announce.add_community(bgp_community_subnet); } // By default use network from attack subnet_cidr_mask_t customer_network; customer_network.subnet_address = current_attack.customer_network.subnet_address; customer_network.cidr_prefix_length = current_attack.customer_network.cidr_prefix_length; unicast_ipv4_announce.set_prefix(customer_network); unicast_ipv4_announce.set_next_hop(gobgp_next_hop_subnet_ipv4); gobgp_client.AnnounceUnicastPrefixLowLevelIPv4(unicast_ipv4_announce, is_withdrawal); } if (fastnetmon_global_configuration.gobgp_announce_host) { IPv4UnicastAnnounce unicast_ipv4_announce; std::vector host_ipv4_communities; host_ipv4_communities.push_back(fastnetmon_global_configuration.gobgp_community_host); for (auto community_string : host_ipv4_communities) { bgp_community_attribute_element_t bgp_community_host; if (!read_bgp_community_from_string(community_string, bgp_community_host)) { logger << log4cpp::Priority::ERROR << "Could not decode BGP community for IPv4 host: " << community_string; // We may have multiple communities and other communities may be correct, skip only broken one continue; } unicast_ipv4_announce.add_community(bgp_community_host); } subnet_cidr_mask_t host_address_as_subnet(client_ip, 32); unicast_ipv4_announce.set_prefix(host_address_as_subnet); unicast_ipv4_announce.set_next_hop(gobgp_next_hop_host_ipv4); gobgp_client.AnnounceUnicastPrefixLowLevelIPv4(unicast_ipv4_announce, is_withdrawal); } } void gobgp_ban_manage(const std::string& action, bool ipv6, uint32_t client_ip, const subnet_ipv6_cidr_mask_t& client_ipv6, const attack_details_t& current_attack) { GrpcClient gobgp_client = GrpcClient(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials())); bool is_withdrawal = false; std::string action_name; if (action == "ban") { is_withdrawal = false; action_name = "announce"; } else { is_withdrawal = true; action_name = "withdraw"; } if (ipv6) { gobgp_ban_manage_ipv6(gobgp_client, client_ipv6, is_withdrawal, current_attack); } else { gobgp_ban_manage_ipv4(gobgp_client, client_ip, is_withdrawal, current_attack); } } fastnetmon-1.2.8/src/actions/gobgp_action.hpp000066400000000000000000000006341472727706000213120ustar00rootroot00000000000000#pragma once #include "../fastnetmon_types.hpp" #include "..//attack_details.hpp" #include void gobgp_action_init(); void gobgp_action_shutdown(); void gobgp_ban_manage(const std::string& action, bool ipv6, uint32_t client_ip, const subnet_ipv6_cidr_mask_t& client_ipv6, const attack_details_t& current_attack); fastnetmon-1.2.8/src/afpacket_plugin/000077500000000000000000000000001472727706000176375ustar00rootroot00000000000000fastnetmon-1.2.8/src/afpacket_plugin/afpacket_collector.cpp000066400000000000000000000435711472727706000242010ustar00rootroot00000000000000#include "../all_logcpp_libraries.hpp" #include "../fastnetmon_plugin.hpp" #include #include #include "../fast_library.hpp" // For support uint32_t, uint16_t #include // For config map operations #include #include #include #include #include #include "../simple_packet_parser_ng.hpp" #include #include #include #include "afpacket_collector.hpp" #include #include #include #include /* the L2 protocols */ #include #include #include #include #include #include // for struct sock_filter #include "../fastnetmon_configuration_scheme.hpp" extern fastnetmon_configuration_t fastnetmon_global_configuration; // Get log4cpp logger from main programme extern log4cpp::Category& logger; // Pass unparsed packets number to main programme extern uint64_t total_unparsed_packets; // Global configuration map extern std::map configuration_map; // This variable name should be unique for every plugin! process_packet_pointer afpacket_process_func_ptr = NULL; std::string socket_received_packets_desc = "Number of received packets"; uint64_t socket_received_packets = 0; std::string socket_dropped_packets_desc = "Number of dropped packets"; uint64_t socket_dropped_packets = 0; std::string blocks_read_desc = "Number of blocks we read from kernel, each block has multiple packets"; uint64_t blocks_read = 0; std::string af_packet_packets_raw_desc = "Number of packets read by AF_PACKET before parsing"; uint64_t af_packet_packets_raw = 0; std::string af_packet_packets_parsed_desc = "Number of parsed packets"; uint64_t af_packet_packets_parsed = 0; std::string af_packet_packets_unparsed_desc = "Number of not parsed packets"; uint64_t af_packet_packets_unparsed = 0; // Default sampling rate uint32_t mirror_af_packet_custom_sampling_rate = 1; // 4194304 bytes unsigned int blocksiz = 1 << 22; // 2048 bytes unsigned int framesiz = 1 << 11; // Number of blocks unsigned int blocknum = 64; struct block_desc { uint32_t version; uint32_t offset_to_priv; struct tpacket_hdr_v1 h1; }; /* * - PACKET_FANOUT_HASH: schedule to socket by skb's packet hash * - PACKET_FANOUT_LB: schedule to socket by round-robin * - PACKET_FANOUT_CPU: schedule to socket by CPU packet arrives on * - PACKET_FANOUT_RND: schedule to socket by random selection * - PACKET_FANOUT_ROLLOVER: if one socket is full, rollover to another * - PACKET_FANOUT_QM: schedule to socket by skbs recorded queue_mapping */ int fanout_type = PACKET_FANOUT_CPU; // Our kernel headers aren't so fresh and we need it #ifndef PACKET_FANOUT_QM #define PACKET_FANOUT_QM 5 #endif void start_af_packet_capture_for_interface(std::string capture_interface, int fanout_group_id, unsigned int num_cpus); // We keep all active AF_PACKET sockets here std::vector active_af_packet_sockets; std::mutex active_af_packet_sockets_mutex; int get_fanout_by_name(std::string fanout_name) { if (fanout_name == "" || fanout_name == "cpu") { // Default mode for backward compatibility return PACKET_FANOUT_CPU; } else if (fanout_name == "lb") { return PACKET_FANOUT_LB; } else if (fanout_name == "hash") { return PACKET_FANOUT_HASH; } else if (fanout_name == "random") { return PACKET_FANOUT_RND; } else if (fanout_name == "rollover") { return PACKET_FANOUT_ROLLOVER; } else if (fanout_name == "queue_mapping") { return PACKET_FANOUT_QM; } else { // Return default one logger << log4cpp::Priority::ERROR << "Unknown FANOUT mode: " << fanout_name << " switched to default (CPU)"; return PACKET_FANOUT_CPU; } } std::vector get_af_packet_stats() { std::vector system_counter; if (true) { // We should iterate over all sockets because every socket has independent counter std::lock_guard lock_guard(active_af_packet_sockets_mutex); for (auto socket : active_af_packet_sockets) { struct tpacket_stats_v3 stats3; memset(&stats3, 0, sizeof(struct tpacket_stats_v3)); socklen_t structure_length = sizeof(struct tpacket_stats_v3); // Each per socket structure will reset to zero after reading // We need to investigate that this system call is thread safe. I did not find any evidence of it and more detailed research is needed int res = getsockopt(socket, SOL_PACKET, PACKET_STATISTICS, &stats3, &structure_length); if (res != 0) { logger << log4cpp::Priority::ERROR << "Cannot read socket stats with error code: " << res; } else { socket_received_packets += stats3.tp_packets; socket_dropped_packets += stats3.tp_drops; } } } system_counter.push_back(system_counter_t("af_packet_socket_received_packets", socket_received_packets, metric_type_t::counter, socket_received_packets_desc)); system_counter.push_back(system_counter_t("af_packet_socket_dropped_packets", socket_dropped_packets, metric_type_t::counter, socket_dropped_packets_desc)); system_counter.push_back(system_counter_t("af_packet_blocks_read", blocks_read, metric_type_t::counter, blocks_read_desc)); system_counter.push_back(system_counter_t("af_packet_packets_raw", af_packet_packets_raw, metric_type_t::counter, af_packet_packets_raw_desc)); system_counter.push_back(system_counter_t("af_packet_packets_parsed", af_packet_packets_parsed, metric_type_t::counter, af_packet_packets_parsed_desc)); system_counter.push_back(system_counter_t("af_packet_packets_unparsed", af_packet_packets_unparsed, metric_type_t::counter, af_packet_packets_unparsed_desc)); return system_counter; } void flush_block(struct block_desc* pbd) { pbd->h1.block_status = TP_STATUS_KERNEL; } void walk_block(struct block_desc* pbd) { struct tpacket3_hdr* ppd = (struct tpacket3_hdr*)((uint8_t*)pbd + pbd->h1.offset_to_first_pkt); for (uint32_t i = 0; i < pbd->h1.num_pkts; ++i) { af_packet_packets_raw++; // struct ethhdr *eth = (struct ethhdr *) ((uint8_t *) ppd + ppd->tp_mac); // Print packets u_char* data_pointer = (u_char*)((uint8_t*)ppd + ppd->tp_mac); simple_packet_t packet; packet.source = MIRROR; packet.arrival_time = current_inaccurate_time; packet.sample_ratio = 1; //-V1048 // Override default sample rate by rate specified in configuration if (mirror_af_packet_custom_sampling_rate > 1) { packet.sample_ratio = mirror_af_packet_custom_sampling_rate; } auto result = parse_raw_packet_to_simple_packet_full_ng((u_char*)data_pointer, ppd->tp_snaplen, ppd->tp_snaplen, packet, fastnetmon_global_configuration.af_packet_extract_tunnel_traffic, fastnetmon_global_configuration.af_packet_read_packet_length_from_ip_header); if (result != network_data_stuctures::parser_code_t::success) { // This counter resets for speed calculation every second total_unparsed_packets++; af_packet_packets_unparsed++; logger << log4cpp::Priority::DEBUG << "Cannot parse packet using ng parser: " << parser_code_to_string(result); } else { af_packet_packets_parsed++; afpacket_process_func_ptr(packet); } // Move pointer to next packet ppd = (struct tpacket3_hdr*)((uint8_t*)ppd + ppd->tp_next_offset); } } void read_packets_from_socket(int packet_socket, struct iovec* rd); // Configures socket and starts traffic capture bool setup_socket(std::string interface_name, bool enable_fanout, 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. Error number: " << errno << " error text: " << strerror(errno); return false; } if (true) { // Add socket to global structure, we will use it to get statistics for each of them std::lock_guard lock_guard(active_af_packet_sockets_mutex); active_af_packet_sockets.push_back(packet_socket); } // We should use V3 because 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 for " << interface_name; return false; } int interface_number = 0; bool get_interface_number_result = get_interface_number_by_device_name(packet_socket, interface_name, interface_number); if (!get_interface_number_result) { logger << log4cpp::Priority::ERROR << "Can't get interface number by interface name for " << interface_name; return false; } // Without PACKET_ADD_MEMBERSHIP step AF_PACKET will capture traffic on all interfaces which is not exactly what we // want in this case We need to specify exact interface we're interested in here // 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 for " << interface_name; return false; } // 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 for " << interface_name; return false; } size_t buffer_size = req.tp_block_size * req.tp_block_nr; logger << log4cpp::Priority::DEBUG << "Allocating " << buffer_size << " byte buffer for AF_PACKET interface: " << interface_name; uint8_t* mapped_buffer = (uint8_t*)mmap(NULL, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, packet_socket, 0); if (mapped_buffer == MAP_FAILED) { logger << log4cpp::Priority::ERROR << "mmap failed errno: " << errno << " error: " << strerror(errno); return false; } // Allocate iov structure for each block struct iovec* rd = (struct iovec*)malloc(req.tp_block_nr * sizeof(struct iovec)); if (rd == NULL) { logger << log4cpp::Priority::ERROR << "Cannot allocate memory for iovecs for " << interface_name; return false; } // Initialise iov structures for (unsigned 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; } 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; 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 for " << interface_name; return false; } if (enable_fanout) { 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 for interface " << interface_name << " error number: " << errno << " error: " << strerror(errno); return false; } } // Start traffic collection loop read_packets_from_socket(packet_socket, rd); return true; } // Reads traffic from iovec using poll void read_packets_from_socket(int packet_socket, struct iovec* rd) { 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; } blocks_read++; walk_block(pbd); flush_block(pbd); current_block_num = (current_block_num + 1) % blocknum; } return; } void start_af_packet_capture(std::string interface_name, bool enable_fanout, int fanout_group_id) { setup_socket(interface_name, enable_fanout, fanout_group_id); } void start_afpacket_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "AF_PACKET plugin started"; afpacket_process_func_ptr = func_ptr; // It's not compatible with Advanced and has no alternative if (configuration_map.count("mirror_af_packet_custom_sampling_rate") != 0) { mirror_af_packet_custom_sampling_rate = convert_string_to_integer(configuration_map["mirror_af_packet_custom_sampling_rate"]); } // Set FANOUT mode fanout_type = get_fanout_by_name(fastnetmon_global_configuration.mirror_af_packet_fanout_mode); unsigned int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); logger.info("We have %d cpus for AF_PACKET", num_cpus); if (fastnetmon_global_configuration.interfaces.size() == 0) { logger << log4cpp::Priority::ERROR << "Please specify intreface for AF_PACKET"; return; } logger << log4cpp::Priority::DEBUG << "AF_PACKET will listen on " << fastnetmon_global_configuration.interfaces.size() << " interfaces"; // Thread group for all "master" processes boost::thread_group af_packet_main_threads; for (std::vector::size_type i = 0; i < fastnetmon_global_configuration.interfaces.size(); i++) { // Use process id to identify particular fanout group int group_identifier = getpid(); // And add number for current interface to distinguish them group_identifier += i; int fanout_group_id = group_identifier & 0xffff; std::string capture_interface = fastnetmon_global_configuration.interfaces[i]; logger << log4cpp::Priority::INFO << "AF_PACKET will listen on " << capture_interface << " interface"; boost::thread* af_packet_interface_thread = new boost::thread(start_af_packet_capture_for_interface, capture_interface, fanout_group_id, num_cpus); af_packet_main_threads.add_thread(af_packet_interface_thread); } af_packet_main_threads.join_all(); } // Starts traffic capture for particular interface void start_af_packet_capture_for_interface(std::string capture_interface, int fanout_group_id, unsigned int num_cpus) { if (num_cpus == 1) { logger << log4cpp::Priority::INFO << "Disable AF_PACKET fanout because you have only single CPU"; bool fanout = false; start_af_packet_capture(capture_interface, fanout, 0); } else { // We have two or more CPUs boost::thread_group packet_receiver_thread_group; for (unsigned int cpu = 0; cpu < num_cpus; cpu++) { logger << log4cpp::Priority::INFO << "Start AF_PACKET worker process for " << capture_interface << " with fanout group id " << fanout_group_id << " on CPU " << cpu; boost::thread::attributes thread_attrs; if (fastnetmon_global_configuration.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"; } } bool fanout = true; packet_receiver_thread_group.add_thread( new boost::thread(thread_attrs, boost::bind(start_af_packet_capture, capture_interface, fanout, fanout_group_id))); } // Wait all processes for finish packet_receiver_thread_group.join_all(); } } fastnetmon-1.2.8/src/afpacket_plugin/afpacket_collector.hpp000066400000000000000000000004371472727706000242000ustar00rootroot00000000000000#pragma once #include "../fastnetmon_types.hpp" void start_afpacket_collection(process_packet_pointer func_ptr); void start_af_packet_capture_for_interface(std::string capture_interface, int fanout_group_id, unsigned int num_cpus); std::vector get_af_packet_stats(); fastnetmon-1.2.8/src/all_logcpp_libraries.hpp000066400000000000000000000006361472727706000213710ustar00rootroot00000000000000#ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated" #endif #include #include #include #include #include #include #include #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif fastnetmon-1.2.8/src/api.cpp000066400000000000000000000460411472727706000157650ustar00rootroot00000000000000#include "api.hpp" #include "fastnetmon_types.hpp" #include "fastnetmon_logic.hpp" #include "attack_details.hpp" #include "ban_list.hpp" ::grpc::Status FastnetmonApiServiceImpl::GetBanlist(::grpc::ServerContext* context, const ::fastnetmoninternal::BanListRequest* request, ::grpc::ServerWriter<::fastnetmoninternal::BanListReply>* writer) { extern blackhole_ban_list_t ban_list_ipv6; extern blackhole_ban_list_t ban_list_ipv4; logger << log4cpp::Priority::INFO << "API we asked for banlist"; // IPv4 std::map ban_list_ipv4_copy; // Get whole ban list content atomically ban_list_ipv4.get_whole_banlist(ban_list_ipv4_copy); for (auto itr : ban_list_ipv4_copy) { fastnetmoninternal::BanListReply reply; reply.set_ip_address(convert_ip_as_uint_to_string(itr.first) + "/32"); writer->Write(reply); } // IPv6 std::map ban_list_ipv6_copy; // Get whole ban list content atomically ban_list_ipv6.get_whole_banlist(ban_list_ipv6_copy); for (auto itr : ban_list_ipv6_copy) { fastnetmoninternal::BanListReply reply; reply.set_ip_address(print_ipv6_cidr_subnet(itr.first)); writer->Write(reply); } return grpc::Status::OK; } ::grpc::Status FastnetmonApiServiceImpl::ExecuteBan(ServerContext* context, const fastnetmoninternal::ExecuteBanRequest* request, fastnetmoninternal::ExecuteBanReply* reply) { extern blackhole_ban_list_t ban_list_ipv6; extern blackhole_ban_list_t ban_list_ipv4; extern patricia_tree_t *lookup_tree_ipv4; extern patricia_tree_t *lookup_tree_ipv6; logger << log4cpp::Priority::INFO << "API we asked for ban for IP: " << request->ip_address(); if (!validate_ipv6_or_ipv4_host(request->ip_address())) { logger << log4cpp::Priority::ERROR << "You specified malformed IP address"; return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Malformed IP address"); } // At this step IP should be valid IPv4 or IPv6 address bool ipv6 = false; if (request->ip_address().find(":") != std::string::npos) { ipv6 = true; } bool ipv4 = !ipv6; uint32_t client_ip = 0; subnet_ipv6_cidr_mask_t ipv6_address; ipv6_address.cidr_prefix_length = 128; attack_details_t current_attack; current_attack.ipv6 = ipv6; // We trigger this action manually current_attack.attack_detection_source = attack_detection_source_t::Manual; boost::circular_buffer empty_simple_packets_buffer; // Empty raw buffer boost::circular_buffer empty_raw_packets_buffer; std::string flow_attack_details = "manually triggered attack"; if (ipv4) { bool parse_res = convert_ip_as_string_to_uint_safe(request->ip_address(), client_ip); if (!parse_res) { logger << log4cpp::Priority::ERROR << "Can't parse IPv4 address: " << request->ip_address(); return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Can't parse IPv4 address"); } subnet_cidr_mask_t subnet; bool lookup_result = lookup_ip_in_integer_form_inpatricia_and_return_subnet_if_found(lookup_tree_ipv4, client_ip, subnet); if (!lookup_result) { logger << log4cpp::Priority::ERROR << "IP address " << request->ip_address() << " does not belong to our networks."; return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "This IP does not belong to our subnets"); } ban_list_ipv4.add_to_blackhole(client_ip, current_attack); } else { bool parsed_ipv6 = read_ipv6_host_from_string(request->ip_address(), ipv6_address.subnet_address); if (!parsed_ipv6) { logger << log4cpp::Priority::ERROR << "Can't parse IPv6 address: " << request->ip_address(); return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Can't parse IPv6 address"); } bool in_our_networks_list = ip_belongs_to_patricia_tree_ipv6(lookup_tree_ipv6, ipv6_address.subnet_address); if (!in_our_networks_list) { logger << log4cpp::Priority::ERROR << "IP address " << request->ip_address() << " is not belongs to our networks."; return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "This IP not belongs to our subnets"); } ban_list_ipv6.add_to_blackhole(ipv6_address, current_attack); } logger << log4cpp::Priority::INFO << "API call ban handlers manually"; call_blackhole_actions_per_host(attack_action_t::ban, client_ip, ipv6_address, ipv6, current_attack, attack_detection_source_t::Automatic, flow_attack_details, empty_simple_packets_buffer, empty_raw_packets_buffer); return grpc::Status::OK; } ::grpc::Status FastnetmonApiServiceImpl::ExecuteUnBan(ServerContext* context, const fastnetmoninternal::ExecuteBanRequest* request, fastnetmoninternal::ExecuteBanReply* reply) { extern blackhole_ban_list_t ban_list_ipv6; extern blackhole_ban_list_t ban_list_ipv4; logger << log4cpp::Priority::INFO << "API: We asked for unban for IP: " << request->ip_address(); if (!validate_ipv6_or_ipv4_host(request->ip_address())) { logger << log4cpp::Priority::ERROR << "You specified malformed IP address"; return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Malformed IP address"); } // At this step IP should be valid IPv4 or IPv6 address bool ipv6 = false; if (request->ip_address().find(":") != std::string::npos) { ipv6 = true; } bool ipv4 = !ipv6; uint32_t client_ip = 0; subnet_ipv6_cidr_mask_t ipv6_address; ipv6_address.cidr_prefix_length = 128; attack_details_t current_attack; if (ipv4) { bool parse_res = convert_ip_as_string_to_uint_safe(request->ip_address(), client_ip); if (!parse_res) { logger << log4cpp::Priority::ERROR << "Can't parse IPv4 address: " << request->ip_address(); return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Can't parse IPv4 address"); } bool is_blackholed_ipv4 = ban_list_ipv4.is_blackholed(client_ip); if (!is_blackholed_ipv4) { logger << log4cpp::Priority::ERROR << "API: Could not find IPv4 address in ban list"; return grpc::Status::CANCELLED; } bool get_details = ban_list_ipv4.get_blackhole_details(client_ip, current_attack); if (!get_details) { return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Could not get IPv4 blackhole details"); } ban_list_ipv4.remove_from_blackhole(client_ip); } else { bool parsed_ipv6 = read_ipv6_host_from_string(request->ip_address(), ipv6_address.subnet_address); if (!parsed_ipv6) { logger << log4cpp::Priority::ERROR << "Can't parse IPv6 address: " << request->ip_address(); return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Can't parse IPv6 address"); } bool is_blackholed_ipv6 = ban_list_ipv6.is_blackholed(ipv6_address); if (!is_blackholed_ipv6) { logger << log4cpp::Priority::ERROR << "API: Could not find IPv6 address in ban list"; return grpc::Status::CANCELLED; } bool get_details = ban_list_ipv6.get_blackhole_details(ipv6_address, current_attack); if (!get_details) { return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Could not get IPv6 blackhole details"); } ban_list_ipv6.remove_from_blackhole(ipv6_address); } // It's empty for unban std::string flow_attack_details; // These are empty too boost::circular_buffer simple_packets_buffer; boost::circular_buffer raw_packets_buffer; call_blackhole_actions_per_host(attack_action_t::unban, client_ip, ipv6_address, ipv6, current_attack, attack_detection_source_t::Automatic, flow_attack_details, simple_packets_buffer, raw_packets_buffer); return grpc::Status::OK; } void fill_total_traffic_counters_api(::grpc::ServerWriter<::fastnetmoninternal::SixtyFourNamedCounter>* writer, const direction_t& packet_direction, const total_speed_counters_t& total_counters, bool return_per_protocol_metrics, const std::string& unit) { std::string direction_as_string = get_direction_name(packet_direction); fastnetmoninternal::SixtyFourNamedCounter reply; reply.set_counter_name(direction_as_string + " traffic"); reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].total.packets); reply.set_counter_unit("pps"); writer->Write(reply); if (return_per_protocol_metrics) { // tcp reply.set_counter_name(direction_as_string + " tcp traffic"); reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].tcp.packets); reply.set_counter_unit("pps"); writer->Write(reply); // udp reply.set_counter_name(direction_as_string + " udp traffic"); reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].udp.packets); reply.set_counter_unit("pps"); writer->Write(reply); // icmp reply.set_counter_name(direction_as_string + " icmp traffic"); reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].icmp.packets); reply.set_counter_unit("pps"); writer->Write(reply); // fragmented reply.set_counter_name(direction_as_string + " fragmented traffic"); reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].fragmented.packets); reply.set_counter_unit("pps"); writer->Write(reply); // tcp_syn reply.set_counter_name(direction_as_string + " tcp_syn traffic"); reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].tcp_syn.packets); reply.set_counter_unit("pps"); writer->Write(reply); // dropped reply.set_counter_name(direction_as_string + " dropped traffic"); reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].dropped.packets); reply.set_counter_unit("pps"); writer->Write(reply); } // Write traffic speed with same name but with other unit reply.set_counter_name(direction_as_string + " traffic"); if (unit == "bps") { reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].total.bytes * 8); reply.set_counter_unit("bps"); } else { reply.set_counter_value( convert_speed_to_mbps(total_counters.total_speed_average_counters[packet_direction].total.bytes)); reply.set_counter_unit("mbps"); } writer->Write(reply); if (return_per_protocol_metrics) { // tcp reply.set_counter_name(direction_as_string + " tcp traffic"); if (unit == "bps") { reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].tcp.bytes * 8); reply.set_counter_unit("bps"); } else { reply.set_counter_value( convert_speed_to_mbps(total_counters.total_speed_average_counters[packet_direction].tcp.bytes)); reply.set_counter_unit("mbps"); } writer->Write(reply); // udp reply.set_counter_name(direction_as_string + " udp traffic"); if (unit == "bps") { reply.set_counter_unit("bps"); reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].udp.bytes * 8); } else { reply.set_counter_value( convert_speed_to_mbps(total_counters.total_speed_average_counters[packet_direction].udp.bytes)); reply.set_counter_unit("mbps"); } writer->Write(reply); // icmp reply.set_counter_name(direction_as_string + " icmp traffic"); if (unit == "bps") { reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].icmp.bytes * 8); reply.set_counter_unit("bps"); } else { reply.set_counter_value( convert_speed_to_mbps(total_counters.total_speed_average_counters[packet_direction].icmp.bytes)); reply.set_counter_unit("mbps"); } writer->Write(reply); // fragmented reply.set_counter_name(direction_as_string + " fragmented traffic"); if (unit == "bps") { reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].fragmented.bytes * 8); reply.set_counter_unit("bps"); } else { reply.set_counter_value( convert_speed_to_mbps(total_counters.total_speed_average_counters[packet_direction].fragmented.bytes)); reply.set_counter_unit("mbps"); } writer->Write(reply); // tcp_syn reply.set_counter_name(direction_as_string + " tcp_syn traffic"); if (unit == "bps") { reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].tcp_syn.bytes * 8); reply.set_counter_unit("bps"); } else { reply.set_counter_value( convert_speed_to_mbps(total_counters.total_speed_average_counters[packet_direction].tcp_syn.bytes)); reply.set_counter_unit("mbps"); } writer->Write(reply); // dropped reply.set_counter_name(direction_as_string + " dropped traffic"); if (unit == "bps") { reply.set_counter_value(total_counters.total_speed_average_counters[packet_direction].dropped.bytes * 8); reply.set_counter_unit("bps"); } else { reply.set_counter_value( convert_speed_to_mbps(total_counters.total_speed_average_counters[packet_direction].dropped.bytes)); reply.set_counter_unit("mbps"); } writer->Write(reply); } } ::grpc::Status FastnetmonApiServiceImpl::GetTotalTrafficCounters([[maybe_unused]] ::grpc::ServerContext* context, const ::fastnetmoninternal::GetTotalTrafficCountersRequest* request, ::grpc::ServerWriter<::fastnetmoninternal::SixtyFourNamedCounter>* writer) { extern uint64_t incoming_total_flows_speed; extern uint64_t outgoing_total_flows_speed; extern total_speed_counters_t total_counters; logger << log4cpp::Priority::DEBUG << "API we asked for GetTotalTrafficCounters"; extern total_speed_counters_t total_counters; std::vector directions = { INCOMING, OUTGOING, INTERNAL, OTHER }; bool get_per_protocol_metrics = request->get_per_protocol_metrics(); extern bool enable_connection_tracking; std::string unit = request->unit(); for (auto packet_direction : directions) { // Forward our total counters to API format fill_total_traffic_counters_api(writer, packet_direction, total_counters, get_per_protocol_metrics, unit); if (enable_connection_tracking) { fastnetmoninternal::SixtyFourNamedCounter reply; std::string direction_as_string = get_direction_name(packet_direction); reply.set_counter_name(direction_as_string + " traffic"); // Populate flow per second rates if (packet_direction == INCOMING) { reply.set_counter_unit("flows"); reply.set_counter_value(incoming_total_flows_speed); writer->Write(reply); } else if (packet_direction == OUTGOING) { reply.set_counter_unit("flows"); reply.set_counter_value(outgoing_total_flows_speed); writer->Write(reply); } } } return grpc::Status::OK; } ::grpc::Status FastnetmonApiServiceImpl::GetTotalTrafficCountersV6([[maybe_unused]] ::grpc::ServerContext* context, const ::fastnetmoninternal::GetTotalTrafficCountersRequest* request, ::grpc::ServerWriter<::fastnetmoninternal::SixtyFourNamedCounter>* writer) { extern total_speed_counters_t total_counters_ipv6; logger << log4cpp::Priority::DEBUG << "API we asked for GetTotalTrafficCountersV6"; std::vector directions = { INCOMING, OUTGOING, INTERNAL, OTHER }; bool get_per_protocol_metrics = request->get_per_protocol_metrics(); std::string unit = request->unit(); for (auto packet_direction : directions) { // Forward our total counters to API format fill_total_traffic_counters_api(writer, packet_direction, total_counters_ipv6, get_per_protocol_metrics, unit); } return grpc::Status::OK; } ::grpc::Status FastnetmonApiServiceImpl::GetTotalTrafficCountersV4([[maybe_unused]] ::grpc::ServerContext* context, const ::fastnetmoninternal::GetTotalTrafficCountersRequest* request, ::grpc::ServerWriter<::fastnetmoninternal::SixtyFourNamedCounter>* writer) { extern uint64_t incoming_total_flows_speed; extern uint64_t outgoing_total_flows_speed; extern total_speed_counters_t total_counters_ipv4; logger << log4cpp::Priority::DEBUG << "API we asked for GetTotalTrafficCounters"; extern total_speed_counters_t total_counters_ipv4; extern bool enable_connection_tracking; std::vector directions = { INCOMING, OUTGOING, INTERNAL, OTHER }; bool get_per_protocol_metrics = request->get_per_protocol_metrics(); std::string unit = request->unit(); for (auto packet_direction : directions) { fill_total_traffic_counters_api(writer, packet_direction, total_counters_ipv4, get_per_protocol_metrics, unit); if (enable_connection_tracking) { fastnetmoninternal::SixtyFourNamedCounter reply; std::string direction_as_string = get_direction_name(packet_direction); reply.set_counter_name(direction_as_string + " traffic"); // Populate flow per second rates if (packet_direction == INCOMING) { reply.set_counter_unit("flows"); reply.set_counter_value(incoming_total_flows_speed); writer->Write(reply); } else if (packet_direction == OUTGOING) { reply.set_counter_unit("flows"); reply.set_counter_value(outgoing_total_flows_speed); writer->Write(reply); } } } return grpc::Status::OK; } fastnetmon-1.2.8/src/api.hpp000066400000000000000000000035271472727706000157740ustar00rootroot00000000000000#include "fastnetmon_internal_api.grpc.pb.h" #include // API declaration using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; class FastnetmonApiServiceImpl final : public fastnetmoninternal::Fastnetmon::Service { ::grpc::Status GetBanlist(::grpc::ServerContext* context, const ::fastnetmoninternal::BanListRequest* request, ::grpc::ServerWriter<::fastnetmoninternal::BanListReply>* writer) override; ::grpc::Status ExecuteBan(ServerContext* context, const fastnetmoninternal::ExecuteBanRequest* request, fastnetmoninternal::ExecuteBanReply* reply) override; ::grpc::Status ExecuteUnBan(ServerContext* context, const fastnetmoninternal::ExecuteBanRequest* request, fastnetmoninternal::ExecuteBanReply* reply) override; ::grpc::Status GetTotalTrafficCounters([[maybe_unused]] ::grpc::ServerContext* context, const ::fastnetmoninternal::GetTotalTrafficCountersRequest* request, ::grpc::ServerWriter<::fastnetmoninternal::SixtyFourNamedCounter>* writer) override; ::grpc::Status GetTotalTrafficCountersV6([[maybe_unused]] ::grpc::ServerContext* context, const ::fastnetmoninternal::GetTotalTrafficCountersRequest* request, ::grpc::ServerWriter<::fastnetmoninternal::SixtyFourNamedCounter>* writer) override; ::grpc::Status GetTotalTrafficCountersV4([[maybe_unused]] ::grpc::ServerContext* context, const ::fastnetmoninternal::GetTotalTrafficCountersRequest* request, ::grpc::ServerWriter<::fastnetmoninternal::SixtyFourNamedCounter>* writer) override; }; fastnetmon-1.2.8/src/attack_details.hpp000066400000000000000000000040551472727706000201740ustar00rootroot00000000000000#pragma once #include #include // structure with attack details class attack_details_t { public: // This operation is very heavy, it may crash in case of entropy shortage and it actually happened to our customer bool generate_uuid() { boost::uuids::random_generator gen; try { attack_uuid = gen(); } catch (...) { return false; } return true; } std::string get_protocol_name() const { if (ipv6) { return "IPv6"; } else { return "IPv4"; } } // Host group for this attack std::string host_group; // Parent hostgroup for host's host group std::string parent_host_group; direction_t attack_direction = OTHER; // first attackpower detected uint64_t attack_power = 0; // max attack power uint64_t max_attack_power = 0; unsigned int attack_protocol = 0; // Separate section with traffic counters subnet_counter_t traffic_counters{}; // Time when we ban this IP time_t ban_timestamp = 0; bool unban_enabled = true; int ban_time = 0; // seconds of the ban // If this attack was detected for IPv6 protocol bool ipv6 = false; subnet_cidr_mask_t customer_network; attack_detection_source_t attack_detection_source = attack_detection_source_t::Automatic; boost::uuids::uuid attack_uuid{}; attack_severity_t attack_severity = ATTACK_SEVERITY_MIDDLE; // Threshold used to trigger this attack attack_detection_threshold_type_t attack_detection_threshold = attack_detection_threshold_type_t::unknown; packet_storage_t pcap_attack_dump; // Direction of threshold used to trigger this attack attack_detection_direction_type_t attack_detection_direction = attack_detection_direction_type_t::unknown; std::string get_attack_uuid_as_string() const { return boost::uuids::to_string(attack_uuid); } }; // TODO: remove it typedef attack_details_t banlist_item_t; fastnetmon-1.2.8/src/ban_list.hpp000066400000000000000000000071511472727706000170130ustar00rootroot00000000000000#pragma once #include #include #include // This class stores blocked with blackhole hosts template class blackhole_ban_list_t { public: blackhole_ban_list_t() { } // Is this host blackholed? bool is_blackholed(TemplateKeyType client_id) { std::lock_guard lock_guard(structure_mutex); return ban_list_storage.count(client_id) > 0; } // Do we have blackhole with certain uuid? // If we have we will return IP address for this mitigation bool is_blackholed_by_uuid(boost::uuids::uuid mitigation_uuid, TemplateKeyType& client_id) { std::lock_guard lock_guard(structure_mutex); auto itr = std::find_if(ban_list_storage.begin(), ban_list_storage.end(), [mitigation_uuid](const std::pair& pair) { return pair.second.attack_uuid == mitigation_uuid; }); if (itr == ban_list_storage.end()) { return false; } client_id = itr->first; return true; } // Add host to blackhole bool add_to_blackhole(TemplateKeyType client_id, attack_details_t current_attack) { std::lock_guard lock_guard(structure_mutex); ban_list_storage[client_id] = current_attack; return true; } bool remove_from_blackhole(TemplateKeyType client_id) { std::lock_guard lock_guard(structure_mutex); ban_list_storage.erase(client_id); return true; } bool remove_from_blackhole_and_keep_copy(TemplateKeyType client_id, attack_details_t& current_attack) { std::lock_guard lock_guard(structure_mutex); // Confirm that we still have this element in storage if (ban_list_storage.count(client_id) == 0) { return false; } // Copy current value current_attack = ban_list_storage[client_id]; // Remove it ban_list_storage.erase(client_id); return true; } // Add blackholed hosts from external storage to internal bool set_whole_banlist(std::map& ban_list_param) { std::lock_guard lock_guard(structure_mutex); // Copy whole content of passed list to current list ban_list_storage.insert(ban_list_param.begin(), ban_list_param.end()); return true; } // Get list of all blackholed hosts bool get_blackholed_hosts(std::vector& blackholed_hosts) { std::lock_guard lock_guard(structure_mutex); for (auto& elem : ban_list_storage) { blackholed_hosts.push_back(elem.first); } return true; } bool get_whole_banlist(std::map& ban_list_copy) { std::lock_guard lock_guard(structure_mutex); // Copy whole content of this structure ban_list_copy.insert(ban_list_storage.begin(), ban_list_storage.end()); return true; } bool get_blackhole_details(TemplateKeyType client_id, banlist_item_t& banlist_item) { std::lock_guard lock_guard(structure_mutex); auto itr = ban_list_storage.find(client_id); if (itr == ban_list_storage.end()) { return false; } banlist_item = itr->second; return true; } private: std::map ban_list_storage; std::mutex structure_mutex; }; fastnetmon-1.2.8/src/bgp_protocol.cpp000066400000000000000000000543331472727706000177100ustar00rootroot00000000000000#include "bgp_protocol.hpp" #include #include "fast_library.hpp" #include "network_data_structures.hpp" #ifdef _WIN32 #include #else #include #include #include #endif #include #include #include #include #include "nlohmann/json.hpp" uint32_t convert_cidr_to_binary_netmask_local_function_copy(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 decode_nlri_ipv4(int len, uint8_t* value, subnet_cidr_mask_t& extracted_prefix) { uint32_t not_used_number_of_scanned_bytes = 0; return decode_bgp_subnet_encoding_ipv4(len, value, extracted_prefix, not_used_number_of_scanned_bytes); } // https://www.ietf.org/rfc/rfc4271.txt /* a) Length: The Length field indicates the length in bits of the IP address prefix. A length of zero indicates a prefix that matches all IP addresses (with prefix, itself, of zero octets). b) Prefix: The Prefix field contains an IP address prefix, followed by the minimum number of trailing bits needed to make the end of the field fall on an octet boundary. Note that the value of trailing bits is irrelevant. */ // https://github.com/Exa-Networks/exabgp/blob/master/lib/exabgp/bgp/message/update/nlri/cidr.py#L81 // https://github.com/osrg/gobgp/blob/d6148c75a30d87c3f8c1d0f68725127e4c5f3a65/packet/bgp.go#L700 bool decode_bgp_subnet_encoding_ipv4(int len, uint8_t* value, subnet_cidr_mask_t& extracted_prefix, uint32_t& parsed_nlri_length) { // We have NLRI only for IPv4 announces! IPv6 and Flow Spec do not use it! if (len == 0 or value == NULL) { logger << log4cpp::Priority::WARN << "NLRI content is blank for this announce"; return false; } uint8_t prefix_bit_length = value[0]; // logger << log4cpp::Priority::WARN << "We extracted prefix length: " << // int(prefix_bit_length) << // std::endl; // Rounds x upward uint32_t prefix_byte_length = how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_bit_length); // 1 means 1 byte size for prefix_bit_length itself uint32_t full_nlri_length = prefix_byte_length + 1; if (len < full_nlri_length) { logger << log4cpp::Priority::WARN << "Not enough data size! We need least " << prefix_byte_length << " bytes of data"; return false; } // We need number of scanned bytes for next parses parsed_nlri_length = full_nlri_length; return decode_bgp_subnet_encoding_ipv4_raw(value, extracted_prefix); } // Same as decode_bgp_subnet_encoding but do not check array bounds at all // We need to ensure about enough length of data array before calling of this // code! bool decode_bgp_subnet_encoding_ipv4_raw(uint8_t* value, subnet_cidr_mask_t& extracted_prefix) { if (value == NULL) { logger << log4cpp::Priority::WARN << "Zero value unexpected for decode_bgp_subnet_encoding_ipv4_raw"; return false; } uint8_t prefix_bit_length = value[0]; uint32_t prefix_byte_length = how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_bit_length); // 1 means 1 byte size for prefix_bit_length itself // uint32_t full_nlri_length = prefix_byte_length + 1; uint32_t prefix_ipv4 = 0; memcpy(&prefix_ipv4, value + 1, prefix_byte_length); // Then we should set to zero all non important bits in address because they // could store weird // information uint32_t subnet_address_netmask_binary = convert_cidr_to_binary_netmask_local_function_copy(prefix_bit_length); // Remove useless bits with this approach prefix_ipv4 = prefix_ipv4 & subnet_address_netmask_binary; // logger << log4cpp::Priority::WARN << "Extracted prefix: " << // convert_ip_as_uint_to_string(prefix_ipv4) << "/" << // int(prefix_bit_length) ; extracted_prefix.subnet_address = prefix_ipv4; extracted_prefix.cidr_prefix_length = prefix_bit_length; return true; } bool decode_ipv6_announce_from_binary_encoded_atributes(std::vector binary_attributes, IPv6UnicastAnnounce& ipv6_announce) { for (auto binary_attribute : binary_attributes) { bgp_attibute_common_header_t bgp_attibute_common_header; bool bgp_attrinute_read_result = bgp_attibute_common_header.parse_raw_bgp_attribute_binary_buffer(binary_attribute); if (!bgp_attrinute_read_result) { logger << log4cpp::Priority::WARN << "Could not read BGP attribute to common structure"; return false; } // bgp_attribute_common_header.print() ; if (bgp_attibute_common_header.attribute_type == BGP_ATTRIBUTE_MP_REACH_NLRI) { bool ipv6_decode_result = decode_mp_reach_ipv6(binary_attribute.get_used_size(), (uint8_t*)binary_attribute.get_pointer(), bgp_attibute_common_header, ipv6_announce); if (!ipv6_decode_result) { logger << log4cpp::Priority::ERROR << "Can't decode IPv6 announce"; return false; } } } return true; } // Decodes MP Reach NLRI attribute and populates IPv6 specific fields bool decode_mp_reach_ipv6(int len, uint8_t* value, bgp_attibute_common_header_t bgp_attibute_common_header, IPv6UnicastAnnounce& ipv6_announce) { // TODO: we should add sanity checks to avoid reads after attribute's memory block uint8_t* mp_reach_attribute_shift = (uint8_t*)value + bgp_attibute_common_header.attribute_body_shift; // Read first part of MP Reach NLRI header bgp_mp_reach_short_header_t* bgp_mp_ext_header = (bgp_mp_reach_short_header_t*)mp_reach_attribute_shift; bgp_mp_ext_header->network_to_host_byte_order(); // logger << log4cpp::Priority::INFO << bgp_mp_ext_header->print(); if (not(bgp_mp_ext_header->afi_identifier == AFI_IP6 and bgp_mp_ext_header->safi_identifier == SAFI_UNICAST)) { logger << log4cpp::Priority::WARN << "We have got unexpected afi or safi numbers from IPv6 MP Reach NLRI"; return false; } subnet_ipv6_cidr_mask_t next_hop_ipv6; // We support only 16 byte (/128) next hops next_hop_ipv6.cidr_prefix_length = 128; //-V1048 if (bgp_mp_ext_header->length_of_next_hop != 16) { logger << log4cpp::Priority::WARN << "We support only 16 byte next hop for IPv6 MP Reach NLRI"; return false; } memcpy(&next_hop_ipv6.subnet_address, mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t), bgp_mp_ext_header->length_of_next_hop); ipv6_announce.set_next_hop(next_hop_ipv6); // logger << log4cpp::Priority::INFO << "IPv6 next hop is: "<< convert_ipv6_subnet_to_string(next_hop_ipv6); // Strip single byte reserved field uint8_t* prefix_length = mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t) + bgp_mp_ext_header->length_of_next_hop + sizeof(uint8_t); // We should cast it to int for proper print // logger << log4cpp::Priority::INFO << "NLRI length: " << int(*prefix_length); uint32_t number_of_bytes_required_for_prefix = how_much_bytes_we_need_for_storing_certain_subnet_mask(*prefix_length); // logger << log4cpp::Priority::INFO << "We need " << number_of_bytes_required_for_prefix << " bytes for this prefix"; subnet_ipv6_cidr_mask_t prefix_ipv6; prefix_ipv6.cidr_prefix_length = *prefix_length; // Strip single byte for prefix_length and read network address memcpy(&prefix_ipv6.subnet_address, prefix_length + sizeof(uint8_t), number_of_bytes_required_for_prefix); ipv6_announce.set_prefix(prefix_ipv6); // logger << log4cpp::Priority::INFO << "Prefix is: " << convert_ipv6_subnet_to_string(prefix_ipv6); return true; } // https://github.com/osrg/gobgp/blob/d6148c75a30d87c3f8c1d0f68725127e4c5f3a65/packet/bgp.go#L5940 bool decode_attribute(int len, char* value, IPv4UnicastAnnounce& unicast_ipv4_announce) { bgp_attibute_common_header_t bgp_attibute_common_header; bool bgp_attrinute_read_result = bgp_attibute_common_header.parse_raw_bgp_attribute((uint8_t*)value, len); if (!bgp_attrinute_read_result) { logger << log4cpp::Priority::WARN << "Could not read BGP attribute to common structure"; return false; } switch (bgp_attibute_common_header.attribute_type) { case BGP_ATTRIBUTE_ORIGIN: { if (bgp_attibute_common_header.attribute_value_length != 1) { logger << log4cpp::Priority::WARN << "Broken size for BGP_ATTRIBUTE_ORIGIN: " << bgp_attibute_common_header.attribute_value_length; return false; } uint8_t origin_value = 0; memcpy(&origin_value, value + bgp_attibute_common_header.attribute_flag_and_type_length + bgp_attibute_common_header.length_of_length_field, sizeof(origin_value)); // logger << log4cpp::Priority::WARN << "BGP_ATTRIBUTE_ORIGIN: " << // get_origin_name_by_value(origin_value) << // std::endl; unicast_ipv4_announce.set_origin((BGP_ORIGIN_TYPES)origin_value); } break; case BGP_ATTRIBUTE_AS_PATH: { logger << log4cpp::Priority::DEBUG << "Got BGP_ATTRIBUTE_AS_PATH but I do not have code for parsing it"; } break; case BGP_ATTRIBUTE_NEXT_HOP: { if (bgp_attibute_common_header.attribute_value_length == 4) { uint32_t nexthop_value = 0; memcpy(&nexthop_value, value + bgp_attibute_common_header.attribute_flag_and_type_length + bgp_attibute_common_header.length_of_length_field, sizeof(nexthop_value)); // logger << log4cpp::Priority::WARN << "BGP_ATTRIBUTE_NEXT_HOP value is: " << convert_ip_as_uint_to_string(nexthop_value); unicast_ipv4_announce.set_next_hop(nexthop_value); } else if (bgp_attibute_common_header.attribute_value_length == 16) { logger << log4cpp::Priority::WARN << "BGP_ATTRIBUTE_NEXT_HOP is not supported yet for IPv6"; } else { logger << log4cpp::Priority::ERROR << "Wrong next hop length: " << bgp_attibute_common_header.attribute_value_length; return false; } } break; case BGP_ATTRIBUTE_MULTI_EXIT_DISC: { logger << log4cpp::Priority::DEBUG << "Got BGP_ATTRIBUTE_MULTI_EXIT_DISC but I do not have code for parsing it"; } break; case BGP_ATTRIBUTE_LOCAL_PREF: { logger << log4cpp::Priority::DEBUG << "Got BGP_ATTRIBUTE_LOCAL_PREF but I do not have code for parsing it"; } break; case BGP_ATTRIBUTE_COMMUNITY: { logger << log4cpp::Priority::DEBUG << "Got BGP_ATTRIBUTE_COMMUNITY but I do not have code for parsing it"; } break; case BGP_ATTRIBUTE_MP_REACH_NLRI: { logger << log4cpp::Priority::WARN << "BGP_ATTRIBUTE_MP_REACH_NLRI with length " << bgp_attibute_common_header.attribute_value_length; // TODO: I call this code only for testing purposes // IPv6UnicastAnnounce ipv6_announce; // decode_mp_reach_ipv6(len, value, bgp_attibute_common_header, ipv6_announce); // logger << log4cpp::Priority::WARN << ipv6_announce.print(); } break; case BGP_ATTRIBUTE_EXTENDED_COMMUNITY: { logger << log4cpp::Priority::DEBUG << "BGP_ATTRIBUTE_EXTENDED_COMMUNITY with length: " << bgp_attibute_common_header.attribute_value_length; } break; default: { logger << log4cpp::Priority::DEBUG << "Unknown attribute: " << int(bgp_attibute_common_header.attribute_type); } break; } return true; } // Prepare MP Reach IPv6 attribute for IPv6 traffic // This function creates only internal payload without attribute headers bool encode_ipv6_announces_into_bgp_mp_reach_attribute_internal(const IPv6UnicastAnnounce& ipv6_announce, dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute) { // Create internal content of IPv6 MP Reach NLRI bgp_mp_reach_ipv6_attribute.set_maximum_buffer_size_in_bytes(2048); /* +---------------------------------------------------------+ | Address Family Identifier (2 octets) | +---------------------------------------------------------+ | Subsequent Address Family Identifier (1 octet) | +---------------------------------------------------------+ | Length of Next Hop Network Address (1 octet) | +---------------------------------------------------------+ | Network Address of Next Hop (variable) | +---------------------------------------------------------+ | Reserved (1 octet) | +---------------------------------------------------------+ | Network Layer Reachability Information (variable) | +---------------------------------------------------------+ */ // Create short header bgp_mp_reach_short_header_t bgp_mp_reach_short_header; bgp_mp_reach_short_header.afi_identifier = AFI_IP6; //-V1048 bgp_mp_reach_short_header.safi_identifier = SAFI_UNICAST; //-V1048 // Add next hop field bgp_mp_reach_short_header.length_of_next_hop = 16; //-V1048 bgp_mp_reach_short_header.host_byte_order_to_network_byte_order(); // Add next hop field bgp_mp_reach_ipv6_attribute.append_data_as_object_ptr(&bgp_mp_reach_short_header); auto next_hop = ipv6_announce.get_next_hop(); logger << log4cpp::Priority::DEBUG << "Append next hop " << convert_ipv6_subnet_to_string(next_hop) << " with length of " << sizeof(next_hop.subnet_address) << " bytes"; bgp_mp_reach_ipv6_attribute.append_data_as_pointer(&next_hop.subnet_address, sizeof(next_hop.subnet_address)); // Append reserved byte uint8_t reserved_byte = 0; bgp_mp_reach_ipv6_attribute.append_byte(reserved_byte); // Get prefix for announce auto prefix = ipv6_announce.get_prefix(); logger << log4cpp::Priority::DEBUG << "Extracted prefix " << convert_ipv6_subnet_to_string(prefix); if (!encode_ipv6_prefix(prefix, bgp_mp_reach_ipv6_attribute)) { logger << log4cpp::Priority::ERROR << "Cannot encode IPv6 prefix"; return false; } return true; } // Encodes IPv6 in BGP encoding format: // Prefix length followed by prefix itself // NB! You have to initialise dynamic_buffer before calling it bool encode_ipv6_prefix(const subnet_ipv6_cidr_mask_t& prefix, dynamic_binary_buffer_t& dynamic_buffer) { // Add prefix length uint8_t prefix_length = uint8_t(prefix.cidr_prefix_length); logger << log4cpp::Priority::DEBUG << "Prefix length: " << uint32_t(prefix_length); if (!dynamic_buffer.append_byte(prefix_length)) { logger << log4cpp::Priority::ERROR << "Cannot add prefix length"; return false; } uint32_t prefix_byte_length = how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_length); logger << log4cpp::Priority::DEBUG << "We need " << int(prefix_byte_length) << " bytes to encode IPv6 prefix " << convert_ipv6_subnet_to_string(prefix); // We should copy only first meaningful bytes if (!dynamic_buffer.append_data_as_pointer(&prefix.subnet_address, prefix_byte_length)) { logger << log4cpp::Priority::ERROR << "Cannot add prefix itself"; return false; } return true; } bool encode_ipv6_announces_into_bgp_mp_reach_attribute(const IPv6UnicastAnnounce& ipv6_announce, dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute) { dynamic_binary_buffer_t mp_nlri_binary_buffer; bool mp_nlri_encode_result = encode_ipv6_announces_into_bgp_mp_reach_attribute_internal(ipv6_announce, mp_nlri_binary_buffer); if (!mp_nlri_encode_result) { logger << log4cpp::Priority::ERROR << "Can't create inner MP Reach attribute for IPv6"; return false; } // uint8_t nlri_length = mp_nlri_binary_buffer.get_used_size(); // logger << log4cpp::Priority::INFO << "Crafter mp reach with size: " << int(nlri_length); // Create attribute header bgp_attribute_multiprotocol_extensions_t bgp_attribute_multiprotocol_extensions; bgp_attribute_multiprotocol_extensions.attribute_length = mp_nlri_binary_buffer.get_used_size(); bgp_mp_reach_ipv6_attribute.set_maximum_buffer_size_in_bytes(2048); bgp_mp_reach_ipv6_attribute.append_data_as_object_ptr(&bgp_attribute_multiprotocol_extensions); bgp_mp_reach_ipv6_attribute.append_dynamic_buffer(mp_nlri_binary_buffer); if (bgp_mp_reach_ipv6_attribute.is_failed()) { logger << log4cpp::Priority::WARN << "We have issues with binary buffer in IPv6 NLRI generation code"; return false; } return true; } // TODO: add sanity checks // If you want to improve this code with eliminating memory copy // Please read this https://en.wikipedia.org/wiki/Return_value_optimization bool encode_bgp_subnet_encoding(const subnet_cidr_mask_t& prefix, dynamic_binary_buffer_t& dynamic_binary_buffer) { uint32_t subnet_address = prefix.subnet_address; uint32_t prefix_bit_length = prefix.cidr_prefix_length; // Rounds x upward uint32_t prefix_byte_length = ceil(float(prefix_bit_length) / 8); // We need 1 byte for prefix length in bits and X bytes for prefix itself uint32_t full_nlri_length = 1 + prefix_byte_length; // logger << log4cpp::Priority::WARN << "We will allocate " << // full_nlri_length << " bytes in buffer" // ; bool allocation_result = dynamic_binary_buffer.set_maximum_buffer_size_in_bytes(full_nlri_length); if (!allocation_result) { logger << log4cpp::Priority::WARN << "Allocation error"; return false; } dynamic_binary_buffer.append_byte(uint8_t(prefix_bit_length)); // Then we should set to zero all non important bits in address because they // could store weird // information uint32_t subnet_address_netmask_binary = convert_cidr_to_binary_netmask_local_function_copy(prefix_bit_length); // Zeroify useless bits subnet_address = subnet_address & subnet_address_netmask_binary; dynamic_binary_buffer.append_data_as_pointer(&subnet_address, prefix_byte_length); return true; } std::string get_origin_name_by_value(uint8_t origin_value) { switch (origin_value) { case BGP_ORIGIN_IGP: return "BGP_ORIGIN_IGP"; break; case BGP_ORIGIN_EGP: return "BGP_ORIGIN_EGP"; break; case BGP_ORIGIN_INCOMPLETE: return "BGP_ORIGIN_INCOMPLETE"; break; default: return "Unknown"; break; } } std::string get_bgp_attribute_name_by_number(uint8_t bgp_attribute_type) { switch (bgp_attribute_type) { case BGP_ATTRIBUTE_ORIGIN: return "BGP_ATTRIBUTE_ORIGIN"; break; case BGP_ATTRIBUTE_AS_PATH: return "BGP_ATTRIBUTE_AS_PATH"; break; case BGP_ATTRIBUTE_NEXT_HOP: return "BGP_ATTRIBUTE_NEXT_HOP"; break; case BGP_ATTRIBUTE_MULTI_EXIT_DISC: return "BGP_ATTRIBUTE_MULTI_EXIT_DISC"; break; case BGP_ATTRIBUTE_LOCAL_PREF: return "BGP_ATTRIBUTE_LOCAL_PREF"; break; case BGP_ATTRIBUTE_ATOMIC_AGGREGATE: return "BGP_ATTRIBUTE_ATOMIC_AGGREGATE"; break; case BGP_ATTRIBUTE_AGGREGATOR: return "BGP_ATTRIBUTE_AGGREGATOR"; break; case BGP_ATTRIBUTE_COMMUNITY: return "BGP_ATTRIBUTE_COMMUNITY"; break; case BGP_ATTRIBUTE_MP_REACH_NLRI: return "BGP_ATTRIBUTE_MP_REACH_NLRI"; break; case BGP_ATTRIBUTE_EXTENDED_COMMUNITY: return "BGP_ATTRIBUTE_EXTENDED_COMMUNITY"; break; default: return "UNKNOWN"; break; } } // This function calculates number of bytes required for store some certain // network // This is very useful if you are working with BGP encoded subnets uint32_t how_much_bytes_we_need_for_storing_certain_subnet_mask(uint8_t prefix_bit_length) { return ceil(float(prefix_bit_length) / 8); } // Wrapper function which just checks correctness of bgp community bool is_bgp_community_valid(std::string community_as_string) { bgp_community_attribute_element_t bgp_community_attribute_element; return read_bgp_community_from_string(community_as_string, bgp_community_attribute_element); } bool read_bgp_community_from_string(std::string community_as_string, bgp_community_attribute_element_t& bgp_community_attribute_element) { std::vector community_as_vector; split(community_as_vector, community_as_string, boost::is_any_of(":"), boost::token_compress_on); if (community_as_vector.size() != 2) { logger << log4cpp::Priority::WARN << "Could not parse community: " << community_as_string; return false; } int asn_as_integer = 0; if (!convert_string_to_positive_integer_safe(community_as_vector[0], asn_as_integer)) { logger << log4cpp::Priority::WARN << "Could not parse ASN from raw format: " << community_as_vector[0]; return false; } int community_number_as_integer = 0; if (!convert_string_to_positive_integer_safe(community_as_vector[1], community_number_as_integer)) { logger << log4cpp::Priority::WARN << "Could not parse community from raw format: " << community_as_vector[0]; return false; } if (asn_as_integer < 0 or community_number_as_integer < 0) { logger << log4cpp::Priority::WARN << "For some strange reasons we've got negative ASN or community numbers"; return false; } if (asn_as_integer > UINT16_MAX) { logger << log4cpp::Priority::ERROR << "Your ASN value exceeds maximum allowed value " << UINT16_MAX; return false; } if (community_number_as_integer > UINT16_MAX) { logger << log4cpp::Priority::ERROR << "Your community value exceeds maximum allowed value " << UINT16_MAX; return false; } bgp_community_attribute_element.asn_number = asn_as_integer; bgp_community_attribute_element.community_number = community_number_as_integer; return true; } fastnetmon-1.2.8/src/bgp_protocol.hpp000066400000000000000000000726331472727706000177200ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "dynamic_binary_buffer.hpp" #include "fast_library.hpp" #include "fastnetmon_networks.hpp" #include "fast_endianless.hpp" #include #include "iana_ip_protocols.hpp" class bgp_attribute_origin; class bgp_attribute_next_hop_ipv4; class IPv4UnicastAnnounce; class IPv6UnicastAnnounce; bool decode_bgp_subnet_encoding_ipv4_raw(uint8_t* value, subnet_cidr_mask_t& extracted_prefix); bool decode_bgp_subnet_encoding_ipv4(int len, uint8_t* value, subnet_cidr_mask_t& extracted_prefix, uint32_t& parsed_nlri_length); uint32_t how_much_bytes_we_need_for_storing_certain_subnet_mask(uint8_t prefix_bit_length); std::string get_bgp_attribute_name_by_number(uint8_t bgp_attribute_type); extern log4cpp::Category& logger; enum BGP_PROTOCOL_MESSAGE_TYPES_UNTYPED : uint8_t { BGP_PROTOCOL_MESSAGE_OPEN = 1, BGP_PROTOCOL_MESSAGE_UPDATE = 2, BGP_PROTOCOL_MESSAGE_NOTIFICATION = 3, BGP_PROTOCOL_MESSAGE_KEEPALIVE = 4, }; // More details here https://tools.ietf.org/html/rfc4271#page-12 class __attribute__((__packed__)) bgp_message_header_t { public: uint8_t marker[16] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; uint16_t length = 0; uint8_t type = 0; void host_byte_order_to_network_byte_order() { length = htons(length); } }; static_assert(sizeof(bgp_message_header_t) == 19, "Broken size for bgp_message_header_t"); // TODO: move to // // https://www.ietf.org/rfc/rfc4271.txt pages 15 and 16 // https://github.com/osrg/gobgp/blob/d6148c75a30d87c3f8c1d0f68725127e4c5f3a65/packet/bgp.go#L3012 struct __attribute__((__packed__)) bgp_attribute_flags { uint8_t : 4, extended_length_bit : 1 = 0, partial_bit : 1 = 0, transitive_bit : 1 = 0, optional_bit : 1 = 0; bgp_attribute_flags(uint8_t flag_as_integer) { memcpy(this, &flag_as_integer, sizeof(flag_as_integer)); } bgp_attribute_flags() { memset(this, 0, sizeof(*this)); } void set_optional_bit(bool bit_value) { optional_bit = (int)bit_value; } void set_transitive_bit(bool bit_value) { transitive_bit = (int)bit_value; } void set_partial_bit(bool bit_value) { partial_bit = (int)bit_value; } void set_extended_length_bit(bool bit_value) { extended_length_bit = (int)bit_value; } bool get_optional_bit() { return optional_bit == 1 ? true : false; } bool get_transitive_bit() { return transitive_bit == 1 ? true : false; } bool get_partial_bit() { return partial_bit == 1 ? true : false; } bool get_extended_length_bit() { return extended_length_bit == 1 ? true : false; } std::string print() const { std::stringstream buf; buf << "optional: " << int(optional_bit) << " transitive: " << int(transitive_bit) << " partial_bit: " << int(partial_bit) << " extended_length_bit: " << int(extended_length_bit); return buf.str(); } }; class bgp_attibute_common_header_t { public: uint8_t attribute_flags = 0; uint8_t attribute_type = 0; uint32_t length_of_length_field = 0; uint32_t attribute_value_length = 0; uint32_t attribute_body_shift = 0; // Just const value uint32_t attribute_flag_and_type_length = 2; std::string print() const { std::stringstream buffer; buffer << "attribute_flags: " << uint32_t(attribute_flags) << " " // << "attribute_pretty_flags: " << // bgp_attribute_flags(attribute_flags).print() << " " << "attribute_type: " << uint32_t(attribute_type) << " " << "attribute_name: " << get_bgp_attribute_name_by_number(attribute_type) << " " << "length_of_length_field: " << length_of_length_field << " " << "attribute_value_length: " << attribute_value_length; return buffer.str(); } // More user friendly form bool parse_raw_bgp_attribute_binary_buffer(dynamic_binary_buffer_t dynamic_binary_buffer) { return parse_raw_bgp_attribute((uint8_t*)dynamic_binary_buffer.get_pointer(), dynamic_binary_buffer.get_used_size()); } bool parse_raw_bgp_attribute(uint8_t* value, size_t len) { if (len < attribute_flag_and_type_length or value == NULL) { logger << log4cpp::Priority::WARN << "Too short attribute. We need least two bytes here but get " << len << " bytes"; return false; } // https://www.ietf.org/rfc/rfc4271.txt page 15 attribute_flags = value[0]; attribute_type = value[1]; bgp_attribute_flags attr_flags(attribute_flags); // attr_flags.print(); length_of_length_field = 1; if (attr_flags.extended_length_bit == 1) { // When we have extended_length_bit we have two bytes for length // information // TODO: add support for this type of attributes // logger << log4cpp::Priority::WARN << "We haven't support for extended // length attributes. // Sorry!" << // std::endl; length_of_length_field = 2; } if (len < attribute_flag_and_type_length + length_of_length_field) { logger << log4cpp::Priority::WARN << "Too short attribute because we need least " << attribute_flag_and_type_length + length_of_length_field << " bytes"; return false; } attribute_value_length = value[2]; // logger << log4cpp::Priority::WARN << "Attribute type: " << // int(attribute_type) ; // logger << log4cpp::Priority::WARN << "Raw attribute length: " << len ; // logger << log4cpp::Priority::WARN << "Attribute internal length: " << // int(attribute_value_length) // ; uint32_t total_attribute_length = attribute_flag_and_type_length + length_of_length_field + attribute_value_length; // logger << log4cpp::Priority::WARN << "attribute_flag_and_type_length: " // << // attribute_flag_and_type_length << // std::endl // << "length_of_length_field: " << length_of_length_field // << "attribute_value_length: " << attribute_value_length ; if (len < total_attribute_length) { logger << log4cpp::Priority::WARN << "Atrribute value length: " << total_attribute_length << " length exceed whole packet length " << len; return false; } // Store shift to attribute payload attribute_body_shift = attribute_flag_and_type_length + length_of_length_field; return true; } }; bool decode_nlri_ipv4(int len, uint8_t* value, subnet_cidr_mask_t& extracted_prefix); bool decode_attribute(int len, char* value, IPv4UnicastAnnounce& unicast_ipv4_announce); bool encode_bgp_subnet_encoding(const subnet_cidr_mask_t& prefix, dynamic_binary_buffer_t& buffer_result); std::string get_origin_name_by_value(uint8_t origin_value); const unsigned int AFI_IP = 1; const unsigned int AFI_IP6 = 2; const unsigned int SAFI_UNICAST = 1; const unsigned int SAFI_FLOW_SPEC_UNICAST = 133; const unsigned int ipv4_unicast_route_family = AFI_IP << 16 | SAFI_UNICAST; const unsigned int ipv4_flow_spec_route_family = AFI_IP << 16 | SAFI_FLOW_SPEC_UNICAST; const unsigned int ipv6_unicast_route_family = AFI_IP6 << 16 | SAFI_UNICAST; const unsigned int ipv6_flow_spec_route_family = AFI_IP6 << 16 | SAFI_FLOW_SPEC_UNICAST; // http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml // https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#transitive enum EXTENDED_COMMUNITY_TYPES_HIGHT_UNTYPED : uint8_t { // Transitive IPv4-Address-Specific Extended Community (Sub-Types are defined in the "Transitive IPv4-Address-Specific Extended Community Sub-Types" registry) EXTENDED_COMMUNITY_TRANSITIVE_IPV4_ADDRESS_SPECIFIC = 1, // 0x01 // We are encoding attributes for BGP flow spec this way // Generic Transitive Experimental Use Extended Community EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL = 128, // 0x80 }; // Subtypes for EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL // http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml // https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#transitive enum EXTENDED_COMMUNITY_TYPES_LOW_FOR_COMMUNITY_TRANSITIVE_EXPEREMENTAL : uint8_t { FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_RATE = 6, FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_ACTION = 7, FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_REDIRECT_AS_TWO_BYTE = 8, FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_REMARKING = 9, }; enum BGP_ATTRIBUTES_TYPES : uint8_t { // https://www.ietf.org/rfc/rfc4271.txt from page 17 BGP_ATTRIBUTE_ORIGIN = 1, BGP_ATTRIBUTE_AS_PATH = 2, BGP_ATTRIBUTE_NEXT_HOP = 3, BGP_ATTRIBUTE_MULTI_EXIT_DISC = 4, BGP_ATTRIBUTE_LOCAL_PREF = 5, BGP_ATTRIBUTE_ATOMIC_AGGREGATE = 6, BGP_ATTRIBUTE_AGGREGATOR = 7, // https://tools.ietf.org/html/rfc1997 from page 1 BGP_ATTRIBUTE_COMMUNITY = 8, // https://tools.ietf.org/html/rfc4760 from page 2 BGP_ATTRIBUTE_MP_REACH_NLRI = 14, // https://tools.ietf.org/html/rfc4360 from page 1 BGP_ATTRIBUTE_EXTENDED_COMMUNITY = 16, }; // https://www.ietf.org/rfc/rfc4271.txt from page 17 enum BGP_ORIGIN_TYPES : uint8_t { BGP_ORIGIN_IGP = 0, BGP_ORIGIN_EGP = 1, BGP_ORIGIN_INCOMPLETE = 2 }; // https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#trans-ipv4 enum BGP_IPV4_EXTENDED_COMMUNITY_SUBTYPES_TRANSITIVE : uint8_t { BGP_IPV4_EXTENDED_COMMUNITY_SUBTYPE_FLOW_SPEC_REDIRECT_IPv4 = 0x0c, }; // It's short MP reach NLRI header. We use it in case when we have non zero length for length_of_next_hop // I use it only to decode/encode IPv6 annnounces class __attribute__((__packed__)) bgp_mp_reach_short_header_t { public: uint16_t afi_identifier = AFI_IP6; uint8_t safi_identifier = SAFI_UNICAST; // According to https://www.ietf.org/rfc/rfc2545.txt we should set it to 16 for global IP addresses uint8_t length_of_next_hop = 16; void network_to_host_byte_order() { afi_identifier = ntohs(afi_identifier); } void host_byte_order_to_network_byte_order() { afi_identifier = htons(afi_identifier); } std::string print() const { std::stringstream buffer; buffer << "afi_identifier: " << uint32_t(afi_identifier) << " " << "safi_identifier: " << uint32_t(safi_identifier) << " " << "length_of_next_hop: " << uint32_t(length_of_next_hop); return buffer.str(); } }; class __attribute__((__packed__)) bgp_attribute_community_t { public: bgp_attribute_community_t() { attribute_type = BGP_ATTRIBUTE_COMMUNITY; // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_optional_bit(true); attribute_flags.set_extended_length_bit(false); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = 0; // This variable store total size in bytes of all community elements (each // element should has // size 4 bytes) uint8_t attribute_length = 0; // Here we have multiple elements of type: bgp_community_attribute_element_t }; // Format of AS_PATH element described here: https://www.rfc-editor.org/rfc/rfc4271.html#page-17 // And with 32 bit / 4 byte ASN corrections from here: https://www.rfc-editor.org/rfc/rfc6793 page 2 // The AS path information exchanged between NEW BGP speakers is carried in the existing AS_PATH attribute, // except that each AS number in the attribute is encoded as a four-octet entity (instead of a two-octet entity). class __attribute__((__packed__)) bgp_as_path_segment_element_t { public: // Path segment type declares on of two possible types of segments: // 1 AS_SET: unordered set of ASes a route in the UPDATE message has traversed // 2 AS_SEQUENCE: ordered set of ASes a route in the UPDATE message has traversed // We use AS_SEQUENCE as default option uint8_t path_segment_type = 2; // The path segment length is a 1-octet length field, containing the number of ASes (not the number of octets) in // the path segment value field. uint8_t path_segment_length = 0; // Here we store list of 4 byte ASNs encoded in 4 byte format each }; // BGP attribute AS_PATH // In RFC this encoding format is covered here: https://www.rfc-editor.org/rfc/rfc4271.html#page-18 class __attribute__((__packed__)) bgp_attribute_as_path_t { public: bgp_attribute_as_path_t() { attribute_type = BGP_ATTRIBUTE_AS_PATH; // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); // It's mandatory: https://www.rfc-editor.org/rfc/rfc4271.html#section-5.1.2 attribute_flags.set_optional_bit(false); // Means that we using single octet length field encoding: // https://www.rfc-editor.org/rfc/rfc4271.html#page-17 attribute_flags.set_extended_length_bit(false); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = 0; // This variable store total size in bytes of all AS_PATH elements uint8_t attribute_length = 0; // Here we have multiple elements of type: bgp_as_path_segment_element_t }; // This is new extended communities: // More details: https://tools.ietf.org/html/rfc4360 class __attribute__((__packed__)) bgp_extended_community_attribute_t { public: bgp_extended_community_attribute_t() { // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_optional_bit(true); attribute_flags.set_extended_length_bit(false); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = BGP_ATTRIBUTE_EXTENDED_COMMUNITY; // This variable store total size in bytes of all community elements. Each // community element has // size 8 bytes uint8_t attribute_length = 0; // Here we have multiple elements of type: bgp_extended_community_element_t }; // BGP extended community attribute class __attribute__((__packed__)) bgp_extended_community_element_t { public: uint8_t type_hight = 0; uint8_t type_low = 0; // This data depends on implementation and values in type_* variables uint8_t value[6] = { 0, 0, 0, 0, 0, 0 }; std::string print() const { std::stringstream buffer; buffer << "type hight: " << uint32_t(type_hight) << " " << "type low: " << uint32_t(type_low) << " " << "value raw: " << print_binary_string_as_hex_with_leading_0x(value, sizeof(value)); return buffer.str(); } }; static_assert(sizeof(bgp_extended_community_element_t) == 8, "Bad size for bgp_extended_community_element_t"); // This is class for storing old style BGP communities which support only 16 // bit AS numbers class __attribute__((__packed__)) bgp_community_attribute_element_t { public: uint16_t asn_number = 0; uint16_t community_number = 0; void host_byte_order_to_network_byte_order() { asn_number = htons(asn_number); community_number = htons(community_number); } }; bool read_bgp_community_from_string(std::string community_as_string, bgp_community_attribute_element_t& bgp_community_attribute_element); static_assert(sizeof(bgp_community_attribute_element_t) == 4, "Broken size of bgp_community_attribute_element_t"); // With this attribute we are encoding different things (flow spec for example) class __attribute__((__packed__)) bgp_attribute_multiprotocol_extensions_t { public: bgp_attribute_multiprotocol_extensions_t() { attribute_flags.set_transitive_bit(false); attribute_flags.set_optional_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_extended_length_bit(false); } std::string print() const { std::stringstream buf; buf << attribute_flags.print() << "attribute type: " << int(attribute_type) << " attribute_length: " << int(attribute_length); return buf.str(); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = BGP_ATTRIBUTE_MP_REACH_NLRI; uint8_t attribute_length = 0; // value has very complex format and we are encoding it with external code }; class __attribute__((__packed__)) bgp_attribute_origin { public: bgp_attribute_origin() { // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_optional_bit(false); attribute_flags.set_extended_length_bit(false); } std::string print() const { std::stringstream buf; buf << attribute_flags.print() << "attribute type: " << int(attribute_type) << " attribute_length: " << int(attribute_length) << " attribute_value: " << attribute_value; return buf.str(); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = BGP_ATTRIBUTE_ORIGIN; uint8_t attribute_length = 1; uint8_t attribute_value = (uint8_t)BGP_ORIGIN_INCOMPLETE; }; class __attribute__((__packed__)) bgp_attribute_next_hop_ipv4 { public: bgp_attribute_next_hop_ipv4() { // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_optional_bit(false); attribute_flags.set_extended_length_bit(false); } bgp_attribute_next_hop_ipv4(uint32_t next_hop) : bgp_attribute_next_hop_ipv4() { attribute_value = next_hop; } bgp_attribute_flags attribute_flags; uint8_t attribute_type = BGP_ATTRIBUTE_NEXT_HOP; uint8_t attribute_length = 4; uint32_t attribute_value = 0; std::string print() const { std::stringstream buf; buf << attribute_flags.print() << "attribute type: " << int(attribute_type) << " attribute_length: " << int(attribute_length) << " attribute_value: " << attribute_value; return buf.str(); } }; class IPv4UnicastAnnounce { public: void set_withdraw(bool withdraw) { this->is_withdraw = withdraw; } bool get_withdraw() { return this->is_withdraw; } void set_next_hop(uint32_t next_hop) { this->next_hop = next_hop; } void set_prefix(subnet_cidr_mask_t prefix) { this->prefix = prefix; } void set_origin(BGP_ORIGIN_TYPES origin) { this->origin = origin; } uint32_t get_next_hop() { return next_hop; } subnet_cidr_mask_t get_prefix() { return this->prefix; } BGP_ORIGIN_TYPES get_origin() { return this->origin; } // Returns prefix in text form std::string get_prefix_in_cidr_form() { return convert_ip_as_uint_to_string(prefix.subnet_address) + "/" + std::to_string(prefix.cidr_prefix_length); } bool generate_nlri(dynamic_binary_buffer_t& buffer_result) const { return encode_bgp_subnet_encoding(this->prefix, buffer_result); } std::vector get_attributes() const { /* * The sender of an UPDATE message SHOULD order path attributes within * the UPDATE message in ascending order of attribute type. The * receiver of an UPDATE message MUST be prepared to handle path * attributes within UPDATE messages that are out of order. */ bgp_attribute_origin origin_attr; // logger << log4cpp::Priority::WARN << "origin_attr: " << // origin_attr.print() << // std::endl; dynamic_binary_buffer_t origin_as_binary_array; origin_as_binary_array.set_maximum_buffer_size_in_bytes(sizeof(origin_attr)); origin_as_binary_array.append_data_as_object_ptr(&origin_attr); bgp_attribute_next_hop_ipv4 next_hop_attr(next_hop); // logger << log4cpp::Priority::WARN << "next_hop_attr: " << // next_hop_attr.print() << // std::endl; dynamic_binary_buffer_t next_hop_as_binary_array; next_hop_as_binary_array.set_maximum_buffer_size_in_bytes(sizeof(next_hop_attr)); next_hop_as_binary_array.append_data_as_object_ptr(&next_hop_attr); // Vector should be ordered in ascending order of attribute types // Check numbers in enum BGP_ATTRIBUTES_TYPES for information std::vector attributes_list; // It has attribute #1 and will be first in all the cases attributes_list.push_back(origin_as_binary_array); // AS Path should be here and it's #2 if (this->as_path_asns.size() > 0) { // We have ASNs for AS_PATH attribute bgp_attribute_as_path_t bgp_attribute_as_path; // Populate attribute length bgp_attribute_as_path.attribute_length = sizeof(bgp_as_path_segment_element_t) + this->as_path_asns.size() * sizeof(uint32_t); logger << log4cpp::Priority::DEBUG << "AS_PATH attribute length: " << uint32_t(bgp_attribute_as_path.attribute_length); uint32_t as_path_attribute_full_length = sizeof(bgp_attribute_as_path_t) + bgp_attribute_as_path.attribute_length; logger << log4cpp::Priority::DEBUG << "AS_PATH attribute full length: " << as_path_attribute_full_length; dynamic_binary_buffer_t as_path_as_binary_array; as_path_as_binary_array.set_maximum_buffer_size_in_bytes(as_path_attribute_full_length); // Append attribute header as_path_as_binary_array.append_data_as_object_ptr(&bgp_attribute_as_path); bgp_as_path_segment_element_t bgp_as_path_segment_element; // Numbers of ASNs in list bgp_as_path_segment_element.path_segment_length = this->as_path_asns.size(); logger << log4cpp::Priority::DEBUG << "AS_PATH segments number: " << uint32_t(bgp_as_path_segment_element.path_segment_length); // Append segment header as_path_as_binary_array.append_data_as_object_ptr(&bgp_as_path_segment_element); logger << log4cpp::Priority::DEBUG << "AS_PATH ASN numner: " << this->as_path_asns.size(); for (auto asn : this->as_path_asns) { // Append all ASNs in big endian encoding uint32_t asn_big_endian = fast_hton(asn); as_path_as_binary_array.append_data_as_object_ptr(&asn_big_endian); } if (as_path_as_binary_array.is_failed()) { logger << log4cpp::Priority::ERROR << "Issue with storing AS_PATH"; } attributes_list.push_back(as_path_as_binary_array); } // Vector should be ordered in ascending order of attribute types // Next hop attribute is #3 attributes_list.push_back(next_hop_as_binary_array); if (community_list.empty()) { return attributes_list; } else { // We have communities bgp_attribute_community_t bgp_attribute_community; // Each record has this of 4 bytes bgp_attribute_community.attribute_length = community_list.size() * sizeof(bgp_community_attribute_element_t); uint32_t community_attribute_full_length = sizeof(bgp_attribute_community_t) + bgp_attribute_community.attribute_length; dynamic_binary_buffer_t communities_list_as_binary_array; communities_list_as_binary_array.set_maximum_buffer_size_in_bytes(community_attribute_full_length); communities_list_as_binary_array.append_data_as_object_ptr(&bgp_attribute_community); for (auto bgp_community_element : community_list) { // Encode they in network byte order bgp_community_element.host_byte_order_to_network_byte_order(); communities_list_as_binary_array.append_data_as_object_ptr(&bgp_community_element); } // Community is attribute #8 attributes_list.push_back(communities_list_as_binary_array); return attributes_list; } } std::string print() const { std::stringstream buf; buf << "Prefix: " << convert_ip_as_uint_to_string(prefix.subnet_address) << "/" << prefix.cidr_prefix_length << " " << "Origin: " << get_origin_name_by_value(origin) << " " << "Next hop: " << convert_ip_as_uint_to_string(next_hop) + "/32"; // TODO: print pretty communities!!! return buf.str(); } // Add multiple communities in single step bool add_multiple_communities(std::vector bgp_communities) { for (auto bgp_community : bgp_communities) { community_list.push_back(bgp_community); } return true; } bool add_community(bgp_community_attribute_element_t bgp_community) { community_list.push_back(bgp_community); return true; } // Add ASN to AS_PATH void add_asn_as_path(uint32_t asn) { as_path_asns.push_back(asn); } // Sets AS_PATH to specified vector of uint32_t void set_as_path(std::vector& as_path_asns_param) { as_path_asns = as_path_asns_param; } private: uint32_t next_hop = 0; subnet_cidr_mask_t prefix; BGP_ORIGIN_TYPES origin = BGP_ORIGIN_INCOMPLETE; bool is_withdraw = false; std::vector community_list; // List of ASNs in 32 bit format. May be empty std::vector as_path_asns; }; class IPv6UnicastAnnounce { public: void set_withdraw(bool withdraw) { this->is_withdraw = withdraw; } bool get_withdraw() { return this->is_withdraw; } void set_next_hop(subnet_ipv6_cidr_mask_t next_hop) { this->next_hop = next_hop; } void set_prefix(subnet_ipv6_cidr_mask_t prefix) { this->prefix = prefix; } void set_origin(BGP_ORIGIN_TYPES origin) { this->origin = origin; } subnet_ipv6_cidr_mask_t get_next_hop() const { return next_hop; } subnet_ipv6_cidr_mask_t get_prefix() const { return this->prefix; } BGP_ORIGIN_TYPES get_origin() const { return this->origin; } std::string print() const { std::stringstream buf; buf << "Prefix: " << convert_ipv6_subnet_to_string(prefix) << " " << "Origin: " << get_origin_name_by_value(origin) << " " << "Next hop: " << convert_ipv6_subnet_to_string(next_hop); return buf.str(); } // Returns prefix in text form std::string get_prefix_in_cidr_form() const { return convert_ipv6_subnet_to_string(prefix); } // Add multiple communities in single step bool add_multiple_communities(std::vector bgp_communities) { for (auto bgp_community : bgp_communities) { community_list.push_back(bgp_community); } return true; } bool add_community(bgp_community_attribute_element_t bgp_community) { community_list.push_back(bgp_community); return true; } std::vector get_communities() const { return community_list; } // Add ASN to AS_PATH void add_asn_as_path(uint32_t asn) { as_path_asns.push_back(asn); } // Sets AS_PATH to specified vector of uint32_t void set_as_path(std::vector& as_path_asns_param) { as_path_asns = as_path_asns_param; } private: subnet_ipv6_cidr_mask_t next_hop{}; subnet_ipv6_cidr_mask_t prefix{}; BGP_ORIGIN_TYPES origin = BGP_ORIGIN_INCOMPLETE; bool is_withdraw = false; std::vector community_list; // TODO: we need to rework AnnounceUnicastPrefixLowLevelIPv6 // and pull all logic from it into get_attributes() right here public: // List of ASNs in 32 bit format. May be empty std::vector as_path_asns; }; static_assert(sizeof(bgp_attribute_flags) == 1, "broken size for bgp_attribute_flags"); static_assert(sizeof(bgp_attribute_origin) == 4, "Bad size for bgp_attribute_origin"); static_assert(sizeof(bgp_attribute_next_hop_ipv4) == 7, "Bad size for bgp_attribute_next_hop_ipv4"); bool is_bgp_community_valid(std::string community_as_string); bool decode_ipv6_announce_from_binary_encoded_atributes(std::vector binary_attributes, IPv6UnicastAnnounce& ipv6_announce); bool decode_mp_reach_ipv6(int len, uint8_t* value, bgp_attibute_common_header_t bgp_attibute_common_header, IPv6UnicastAnnounce& ipv6_announce); bool encode_ipv6_announces_into_bgp_mp_reach_attribute_internal(const IPv6UnicastAnnounce& ipv6_announce, dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute); bool encode_ipv6_announces_into_bgp_mp_reach_attribute(const IPv6UnicastAnnounce& ipv6_announce, dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute); bool encode_ipv6_prefix(const subnet_ipv6_cidr_mask_t& prefix, dynamic_binary_buffer_t& dynamic_buffer); fastnetmon-1.2.8/src/bgp_protocol_flow_spec.cpp000066400000000000000000000757021472727706000217540ustar00rootroot00000000000000#include "bgp_protocol.hpp" #include #include "fast_library.hpp" // inet_ntoa #include "network_data_structures.hpp" #include #include #include #include #include #include #include #include "nlohmann/json.hpp" #include "bgp_protocol_flow_spec.hpp" // We use this encoding in FastNetMon code void uint8t_representation_of_tcp_flags_to_flow_spec(uint8_t tcp_flags, flow_spec_tcp_flagset_t& flagset) { if (extract_bit_value(tcp_flags, TCP_SYN_FLAG_SHIFT)) { flagset.syn_flag = true; } if (extract_bit_value(tcp_flags, TCP_FIN_FLAG_SHIFT)) { flagset.fin_flag = true; } if (extract_bit_value(tcp_flags, TCP_RST_FLAG_SHIFT)) { flagset.rst_flag = true; } if (extract_bit_value(tcp_flags, TCP_PSH_FLAG_SHIFT)) { flagset.psh_flag = true; } if (extract_bit_value(tcp_flags, TCP_ACK_FLAG_SHIFT)) { flagset.ack_flag = true; } if (extract_bit_value(tcp_flags, TCP_URG_FLAG_SHIFT)) { flagset.urg_flag = true; } } bool read_flow_spec_fragmentation_types_from_string(const std::string& string_form, flow_spec_fragmentation_types_t& fragment_flag) { // Unify case for better experience with this function std::string string_form_lowercase = boost::algorithm::to_lower_copy(string_form); if (string_form_lowercase == "dont-fragment") { fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_DONT_FRAGMENT; } else if (string_form_lowercase == "is-fragment") { fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_IS_A_FRAGMENT; } else if (string_form_lowercase == "first-fragment") { fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_FIRST_FRAGMENT; } else if (string_form_lowercase == "last-fragment") { fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_LAST_FRAGMENT; } else if (string_form_lowercase == "not-a-fragment") { fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_NOT_A_FRAGMENT; } else { return false; } return true; } std::string flow_spec_fragmentation_flags_to_string(flow_spec_fragmentation_types_t const& fragment_flag) { // https://github.com/Exa-Networks/exabgp/blob/71157d560096ec20084cf96cfe0f60203721e93b/lib/exabgp/protocol/ip/fragment.py if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_DONT_FRAGMENT) { return "dont-fragment"; } else if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_IS_A_FRAGMENT) { return "is-fragment"; } else if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_FIRST_FRAGMENT) { return "first-fragment"; } else if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_LAST_FRAGMENT) { return "last-fragment"; } else if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_NOT_A_FRAGMENT) { return "not-a-fragment"; } else { return ""; } } bool read_flow_spec_action_type_from_string(const std::string& string_form, bgp_flow_spec_action_types_t& action_type) { if (string_form == "accept") { action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_ACCEPT; } else if (string_form == "discard") { action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_DISCARD; } else if (string_form == "rate-limit") { action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT; } else if (string_form == "redirect") { action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT; } else if (string_form == "mark") { action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_MARK; } else { return false; } return true; } std::string serialize_action_type(const bgp_flow_spec_action_types_t& action_type) { if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_ACCEPT) { return "accept"; } else if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_DISCARD) { return "discard"; } else if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT) { return "rate-limit"; } else if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT) { return "redirect"; } else if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_MARK) { return "mark"; } else { // TODO: add return code for notifying about this case return std::string(""); } } bool read_flow_spec_tcp_flags_from_strig(const std::string& string_form, flow_spec_tcp_flagset_t& flagset) { // Unify case for better experience with this function std::string string_form_lowercase = boost::algorithm::to_lower_copy(string_form); std::vector tcp_flags; // Split line by "|" boost::split(tcp_flags, string_form_lowercase, boost::is_any_of("|"), boost::token_compress_on); for (auto tcp_flag_string : tcp_flags) { if (tcp_flag_string == "syn") { flagset.syn_flag = true; } else if (tcp_flag_string == "ack") { flagset.ack_flag = true; } else if (tcp_flag_string == "fin") { flagset.fin_flag = true; } else if (tcp_flag_string == "urgent") { flagset.urg_flag = true; } else if (tcp_flag_string == "push") { flagset.psh_flag = true; } else if (tcp_flag_string == "rst") { flagset.rst_flag = true; } else { return false; } } return true; } std::string flow_spec_tcp_flagset_to_string(flow_spec_tcp_flagset_t const& tcp_flagset) { std::vector output; if (tcp_flagset.syn_flag) { output.push_back("syn"); } if (tcp_flagset.ack_flag) { output.push_back("ack"); } if (tcp_flagset.fin_flag) { output.push_back("fin"); } if (tcp_flagset.rst_flag) { output.push_back("rst"); } if (tcp_flagset.urg_flag) { output.push_back("urgent"); } if (tcp_flagset.psh_flag) { output.push_back("push"); } return boost::algorithm::join(output, "|"); } bool operator==(const bgp_flow_spec_action_t& lhs, const bgp_flow_spec_action_t& rhs) { if (lhs.get_type() != rhs.get_type()) { return false; } // Action types are equal if (lhs.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT) { return lhs.get_rate_limit() == rhs.get_rate_limit(); } else { return true; } } bool operator!=(const bgp_flow_spec_action_t& lhs, const bgp_flow_spec_action_t& rhs) { return !(lhs == rhs); } // It does not check UUID bool operator==(const flow_spec_rule_t& lhs, const flow_spec_rule_t& rhs) { // Compare source subnets // IPv4 if (lhs.source_subnet_ipv4_used != rhs.source_subnet_ipv4_used) { return false; } else { if (lhs.source_subnet_ipv4_used) { // If they have values if (lhs.source_subnet_ipv4 != rhs.source_subnet_ipv4) { return false; } } } // IPv6 if (lhs.source_subnet_ipv6_used != rhs.source_subnet_ipv6_used) { return false; } else { if (lhs.source_subnet_ipv6_used) { // If they have values if (lhs.source_subnet_ipv6 != rhs.source_subnet_ipv6) { return false; } } } // Compare destination subnets // IPv4 if (lhs.destination_subnet_ipv4_used != rhs.destination_subnet_ipv4_used) { return false; } else { if (lhs.destination_subnet_ipv4_used) { if (lhs.destination_subnet_ipv4 != rhs.destination_subnet_ipv4) { return false; } } } // IPv6 if (lhs.destination_subnet_ipv6_used != rhs.destination_subnet_ipv6_used) { return false; } else { if (lhs.destination_subnet_ipv6_used) { if (lhs.destination_subnet_ipv6 != rhs.destination_subnet_ipv6) { return false; } } } // Compare actions if (lhs.action != rhs.action) { return false; } if (lhs.source_ports != rhs.source_ports) { return false; } if (lhs.destination_ports != rhs.destination_ports) { return false; } if (lhs.packet_lengths != rhs.packet_lengths) { return false; } // This one is non standard compliant field and it cannot be used for BGP flow spec announces if (lhs.vlans != rhs.vlans) { return false; } // This one is non standard compliant field and it cannot be used for BGP flow spec announces if (lhs.ttls != rhs.ttls) { return false; } if (lhs.ipv4_nexthops != rhs.ipv4_nexthops) { return false; } if (lhs.protocols != rhs.protocols) { return false; } if (lhs.tcp_flags != rhs.tcp_flags) { return false; } if (lhs.fragmentation_flags != rhs.fragmentation_flags) { return false; } return true; } bool operator!=(const flow_spec_rule_t& lhs, const flow_spec_rule_t& rhs) { return !(lhs == rhs); } bool operator!=(const flow_spec_tcp_flagset_t& lhs, const flow_spec_tcp_flagset_t& rhs) { return !(lhs == rhs); } bool operator==(const flow_spec_tcp_flagset_t& lhs, const flow_spec_tcp_flagset_t& rhs) { if (lhs.syn_flag == rhs.syn_flag && lhs.ack_flag == rhs.ack_flag && lhs.rst_flag == rhs.rst_flag && lhs.psh_flag == rhs.psh_flag && lhs.urg_flag == rhs.urg_flag && lhs.fin_flag == rhs.fin_flag) { return true; } else { return false; } } /* { "source_prefix": "4.0.0.0\/24", "destination_prefix": "127.0.0.0\/24", "destination_ports": [ 80 ], "source_ports": [ 53, 5353 ], "packet_lengths": [ 777, 1122 ], "protocols": [ "tcp" ], "fragmentation_flags":[ "is-fragment", "dont-fragment" ], "tcp_flags": [ "syn" ], "action_type": "rate-limit", "action": { "rate": 1024 } } */ bool read_flow_spec_from_json_to_native_format(const std::string& json_encoded_flow_spec, flow_spec_rule_t& flow_spec_rule, bool require_action) { using json = nlohmann::json; // We explicitly disable exceptions auto json_doc = json::parse(json_encoded_flow_spec, nullptr, false); if (json_doc.is_discarded()) { logger << log4cpp::Priority::ERROR << "Cannot decode Flow Spec rule from JSON: '" << json_encoded_flow_spec << "'"; return false; } if (json_doc.contains("source_prefix")) { std::string source_prefix_string; try { source_prefix_string = json_doc["source_prefix"].get(); } catch (...) { logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded source_prefix"; return false; } if (source_prefix_string.find(":") != std::string::npos) { subnet_ipv6_cidr_mask_t subnet_cidr_mask; bool conversion_result = read_ipv6_subnet_from_string(subnet_cidr_mask, source_prefix_string); if (!conversion_result) { logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded IPv6 source_prefix"; return false; } flow_spec_rule.set_source_subnet_ipv6(subnet_cidr_mask); } else { subnet_cidr_mask_t subnet_cidr_mask; bool conversion_result = convert_subnet_from_string_to_binary_with_cidr_format_safe(source_prefix_string, subnet_cidr_mask); if (!conversion_result) { logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded source_prefix"; return false; } flow_spec_rule.set_source_subnet_ipv4(subnet_cidr_mask); } } if (json_doc.contains("destination_prefix")) { std::string destination_prefix_string; try { destination_prefix_string = json_doc["destination_prefix"].get(); } catch (...) { logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded destination_prefix"; return false; } if (destination_prefix_string.find(":") != std::string::npos) { subnet_ipv6_cidr_mask_t subnet_cidr_mask; bool conversion_result = read_ipv6_subnet_from_string(subnet_cidr_mask, destination_prefix_string); if (!conversion_result) { logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded IPv6 destination_prefix"; return false; } flow_spec_rule.set_destination_subnet_ipv6(subnet_cidr_mask); } else { subnet_cidr_mask_t subnet_cidr_mask; bool conversion_result = convert_subnet_from_string_to_binary_with_cidr_format_safe(destination_prefix_string, subnet_cidr_mask); if (!conversion_result) { logger << log4cpp::Priority::ERROR << "Could not parse json encoded destination_prefix"; return false; } flow_spec_rule.set_destination_subnet_ipv4(subnet_cidr_mask); } } if (json_doc.contains("destination_ports")) { std::vector ports_vector_as_ints; try { ports_vector_as_ints = json_doc["destination_ports"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode destination_ports " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode destination_ports"; return false; } for (auto port : ports_vector_as_ints) { if (!valid_port(port)) { logger << log4cpp::Priority::ERROR << "Could not parse destination_ports element: bad range " << port; return false; } flow_spec_rule.add_destination_port(port); } } if (json_doc.contains("source_ports")) { std::vector ports_vector_as_ints; try { ports_vector_as_ints = json_doc["source_ports"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode source_ports " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode source_ports"; return false; } for (auto port : ports_vector_as_ints) { if (!valid_port(port)) { logger << log4cpp::Priority::ERROR << "Could not parse source_ports element: bad range " << port; return false; } flow_spec_rule.add_source_port(port); } } if (json_doc.contains("packet_lengths")) { std::vector packet_lengths_vector_as_ints; try { packet_lengths_vector_as_ints = json_doc["packet_lengths"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode packet_lengths " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode packet_lengths"; return false; } for (auto packet_length : packet_lengths_vector_as_ints) { if (packet_length < 0) { logger << log4cpp::Priority::ERROR << "Could not parse packet_lengths element, it must be positive: " << packet_length; return false; } // Should we drop it? if (packet_length > 1500) { logger << log4cpp::Priority::ERROR << "Could not parse packet_lengths element, it must not exceed 1500: " << packet_length; return false; } flow_spec_rule.add_packet_length(packet_length); } } // TODO: this logic is not covered by tests if (json_doc.contains("vlans")) { std::vector vlans_vector_as_ints; try { vlans_vector_as_ints = json_doc["vlans"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode vlans " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode vlans"; return false; } for (auto vlan : vlans_vector_as_ints) { if (vlan < 0) { logger << log4cpp::Priority::ERROR << "Could not parse vlan element, bad range: " << vlan; return false; } flow_spec_rule.add_vlan(vlan); } } // TODO: this logic is not covered by tests if (json_doc.contains("ttls")) { // TODO: I'm not sure that it can handle such small unsigned well std::vector ttls_vector_as_ints; try { ttls_vector_as_ints = json_doc["ttls"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode TTLs " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode TTLs"; return false; } for (auto ttl : ttls_vector_as_ints) { flow_spec_rule.add_ttl(ttl); } } if (json_doc.contains("protocols")) { std::vector protocols_vector_as_strings; try { protocols_vector_as_strings = json_doc["protocols"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode protocols " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode protocols"; return false; } for (const auto& protocol_as_string : protocols_vector_as_strings) { ip_protocol_t protocol; bool result = read_protocol_from_string(protocol_as_string, protocol); if (!result) { logger << log4cpp::Priority::ERROR << "Could not parse this " << protocol_as_string << " as protocol"; return false; } flow_spec_rule.add_protocol(protocol); } } // TODO: this logic is not covered by tests if (json_doc.contains("ipv4_nexthops")) { std::vector next_hops_vector_as_strings; try { next_hops_vector_as_strings = json_doc["ipv4_nexthops"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode ipv4_nexthops " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode ipv4_nexthops"; return false; } for (const auto& next_hop_as_string : next_hops_vector_as_strings) { uint32_t next_hop_ipv4 = 0; auto ip_parser_result = convert_ip_as_string_to_uint_safe(next_hop_as_string, next_hop_ipv4); if (!ip_parser_result) { logger << log4cpp::Priority::ERROR << "Could not parse this " << next_hop_as_string << " as IPv4 address"; return false; } flow_spec_rule.add_ipv4_nexthop(next_hop_ipv4); } } if (json_doc.contains("fragmentation_flags")) { std::vector fragmentation_flags_vector_as_strings; try { fragmentation_flags_vector_as_strings = json_doc["fragmentation_flags"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode fragmentation_flags " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode fragmentation_flags"; return false; } for (const auto& fragmentation_flag_as_string : fragmentation_flags_vector_as_strings) { flow_spec_fragmentation_types_t fragment_flag; bool result = read_flow_spec_fragmentation_types_from_string(fragmentation_flag_as_string, fragment_flag); if (!result) { logger << log4cpp::Priority::ERROR << "Could not parse this " << fragmentation_flag_as_string << " as flow spec fragmentation flag"; return false; } flow_spec_rule.add_fragmentation_flag(fragment_flag); } } if (json_doc.contains("tcp_flags")) { std::vector tcp_flags_vector_as_strings; try { tcp_flags_vector_as_strings = json_doc["tcp_flags"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode tcp_flags " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode tcp_flags"; return false; } for (const auto& tcp_flag_as_string : tcp_flags_vector_as_strings) { flow_spec_tcp_flagset_t flagset; bool result = read_flow_spec_tcp_flags_from_strig(tcp_flag_as_string, flagset); if (!result) { logger << log4cpp::Priority::ERROR << "Could not parse this " << tcp_flag_as_string << " as flow spec tcp option flag"; return false; } flow_spec_rule.add_tcp_flagset(flagset); } } // Skip action section when we do not need it if (!require_action) { return true; } bgp_flow_spec_action_t bgp_flow_spec_action; if (!json_doc.contains("action_type")) { logger << log4cpp::Priority::ERROR << "We have no action_type in JSON and it's mandatory"; return false; } std::string action_as_string; try { action_as_string = json_doc["action_type"].get(); } catch (...) { logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded action_type"; return false; } bgp_flow_spec_action_types_t action_type; bool result = read_flow_spec_action_type_from_string(action_as_string, action_type); if (!result) { logger << log4cpp::Priority::ERROR << "Could not parse action type: " << action_as_string; return false; } bgp_flow_spec_action.set_type(action_type); // And in this case we should extract rate_limit number if (bgp_flow_spec_action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT) { if (json_doc.contains("action")) { auto json_action_doc = json_doc["action"]; if (!json_action_doc.contains("rate")) { logger << log4cpp::Priority::ERROR << "Absent rate argument for rate limit action"; return false; } int32_t rate = 0; try { rate = json_action_doc["rate"].get(); } catch (...) { logger << log4cpp::Priority::ERROR << "Could not parse JSON document for rate"; return false; } if (rate < 0) { logger << log4cpp::Priority::ERROR << "Rate validation failed, it must be positive: " << rate; return false; } bgp_flow_spec_action.set_rate_limit(rate); } else { // We assume zero rate in this case } } else if (bgp_flow_spec_action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT) { if (!json_doc.contains("action")) { logger << log4cpp::Priority::ERROR << "Action need to be provided for redirect"; return false; } auto json_action_doc = json_doc["action"]; if (!json_action_doc.contains("redirect_target_as")) { logger << log4cpp::Priority::ERROR << "Absent redirect_target_as argument for redirect action"; return false; } uint16_t redirect_target_as = 0; try { redirect_target_as = json_action_doc["redirect_target_as"].get(); } catch (...) { logger << log4cpp::Priority::ERROR << "Could not parse JSON document for redirect_target_as"; return false; } bgp_flow_spec_action.set_redirect_as(redirect_target_as); uint32_t redirect_target_value = 0; try { redirect_target_value = json_action_doc["redirect_target_value"].get(); } catch (...) { logger << log4cpp::Priority::ERROR << "Could not parse JSON document for redirect_target_value"; return false; } bgp_flow_spec_action.set_redirect_value(redirect_target_value); } flow_spec_rule.set_action(bgp_flow_spec_action); return true; } // Encode flow spec announce into JSON representation bool encode_flow_spec_to_json(const flow_spec_rule_t& flow_spec_rule, std::string& json_encoded_flow_spec, bool add_uuid) { nlohmann::json flow_json; bool encoding_result = encode_flow_spec_to_json_raw(flow_spec_rule, add_uuid, flow_json); if (!encoding_result) { logger << log4cpp::Priority::ERROR << "Cannot encode Flow Spec into JSON"; return false; } std::string json_as_text = flow_json.dump(); // Remove ugly useless escaping for flow spec destination and source subnets // I.e. 127.0.0.1\/32 boost::replace_all(json_as_text, "\\", ""); json_encoded_flow_spec = json_as_text; return true; } // Encode flow spec in JSON object representation bool encode_flow_spec_to_json_raw(const flow_spec_rule_t& flow_spec_rule, bool add_uuid, nlohmann::json& flow_json) { // UUID is quite important for us, let's add it if (add_uuid) { flow_json["uuid"] = flow_spec_rule.get_announce_uuid_as_string(); } if (flow_spec_rule.source_subnet_ipv4_used) { flow_json["source_prefix"] = convert_ipv4_subnet_to_string(flow_spec_rule.source_subnet_ipv4); } else if (flow_spec_rule.source_subnet_ipv6_used) { flow_json["source_prefix"] = convert_ipv6_subnet_to_string(flow_spec_rule.source_subnet_ipv6); } if (flow_spec_rule.destination_subnet_ipv4_used) { flow_json["destination_prefix"] = convert_ipv4_subnet_to_string(flow_spec_rule.destination_subnet_ipv4); } else if (flow_spec_rule.destination_subnet_ipv6_used) { flow_json["destination_prefix"] = convert_ipv6_subnet_to_string(flow_spec_rule.destination_subnet_ipv6); } if (!flow_spec_rule.destination_ports.empty()) { flow_json["destination_ports"] = flow_spec_rule.destination_ports; } if (!flow_spec_rule.source_ports.empty()) { flow_json["source_ports"] = flow_spec_rule.source_ports; } if (!flow_spec_rule.packet_lengths.empty()) { flow_json["packet_lengths"] = flow_spec_rule.packet_lengths; } if (!flow_spec_rule.vlans.empty()) { flow_json["vlans"] = flow_spec_rule.vlans; } if (!flow_spec_rule.ttls.empty()) { flow_json["ttls"] = flow_spec_rule.ttls; } if (!flow_spec_rule.protocols.empty()) { flow_json["protocols"] = nlohmann::json::array(); for (auto protocol : flow_spec_rule.protocols) { std::string protocol_name = get_ip_protocol_name(protocol); // We use lowercase format boost::algorithm::to_lower(protocol_name); flow_json["protocols"].push_back(protocol_name); } } if (!flow_spec_rule.fragmentation_flags.empty()) { flow_json["fragmentation_flags"] = nlohmann::json::array(); for (auto fragment_flag : flow_spec_rule.fragmentation_flags) { std::string fragmentation_flag_as_string = flow_spec_fragmentation_flags_to_string(fragment_flag); // For some reasons we cannot convert it to string if (fragmentation_flag_as_string == "") { continue; } flow_json["fragmentation_flags"].push_back(fragmentation_flag_as_string); } } // If we have TCP in protocols list explicitly, we add flags bool we_have_tcp_protocol_in_list = find(flow_spec_rule.protocols.begin(), flow_spec_rule.protocols.end(), ip_protocol_t::TCP) != flow_spec_rule.protocols.end(); if (!flow_spec_rule.tcp_flags.empty() && we_have_tcp_protocol_in_list) { flow_json["tcp_flags"] = nlohmann::json::array(); for (auto tcp_flag : flow_spec_rule.tcp_flags) { std::string tcp_flags_as_string = flow_spec_tcp_flagset_to_string(tcp_flag); // For some reasons we cannot encode it, skip iteration if (tcp_flags_as_string == "") { continue; } flow_json["tcp_flags"].push_back(tcp_flags_as_string); } } // Encode action structure flow_json["action_type"] = serialize_action_type(flow_spec_rule.action.get_type()); // We add sub document action when arguments needed if (flow_spec_rule.action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT) { nlohmann::json action_json; action_json["rate"] = flow_spec_rule.action.get_rate_limit(); flow_json["action"] = action_json; } else if (flow_spec_rule.action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT) { nlohmann::json action_json; action_json["redirect_target_as"] = flow_spec_rule.action.get_redirect_as(); action_json["redirect_target_value"] = flow_spec_rule.action.get_redirect_value(); flow_json["action"] = action_json; } return true; } bgp_flowspec_one_byte_byte_encoded_tcp_flags_t return_in_one_byte_encoding(const flow_spec_tcp_flagset_t& flagset) { bgp_flowspec_one_byte_byte_encoded_tcp_flags_t one_byte_flags{}; if (flagset.syn_flag) { one_byte_flags.syn = 1; } if (flagset.fin_flag) { one_byte_flags.fin = 1; } if (flagset.urg_flag) { one_byte_flags.urg = 1; } if (flagset.ack_flag) { one_byte_flags.ack = 1; } if (flagset.psh_flag) { one_byte_flags.psh = 1; } if (flagset.rst_flag) { one_byte_flags.rst = 1; } return one_byte_flags; } flow_spec_tcp_flagset_t convert_one_byte_encoding_to_flowset(const bgp_flowspec_one_byte_byte_encoded_tcp_flags_t& one_byte_flags) { flow_spec_tcp_flagset_t flagset; if (one_byte_flags.syn == 1) { flagset.syn_flag = true; } if (one_byte_flags.fin == 1) { flagset.fin_flag = true; } if (one_byte_flags.urg == 1) { flagset.urg_flag = true; } if (one_byte_flags.ack == 1) { flagset.ack_flag = true; } if (one_byte_flags.psh == 1) { flagset.psh_flag = true; } if (one_byte_flags.rst == 1) { flagset.rst_flag = true; } return flagset; } // Is it range valid for port? bool valid_port(int32_t port) { return port >= 0 && port <= 65535; } fastnetmon-1.2.8/src/bgp_protocol_flow_spec.hpp000066400000000000000000000632601472727706000217550ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "fast_endianless.hpp" #include "dynamic_binary_buffer.hpp" #include "fast_library.hpp" #include "fastnetmon_networks.hpp" #include #include "iana_ip_protocols.hpp" #include "bgp_protocol.hpp" class bgp_flow_spec_action_t; // This structure stores TCP flags in very human friendly way // It could store multiple enabled flags in same time class flow_spec_tcp_flagset_t { public: bool syn_flag = false; bool ack_flag = false; bool fin_flag = false; bool psh_flag = false; bool rst_flag = false; bool urg_flag = false; // Do we have least one flag enabled? bool we_have_least_one_flag_enabled() const { return syn_flag || fin_flag || urg_flag || ack_flag || psh_flag || rst_flag; } std::string print() const { std::stringstream buffer; buffer << "syn: " << syn_flag << " " << "ack: " << ack_flag << " " << "fin: " << fin_flag << " " << "psh: " << psh_flag << " " << "rst: " << rst_flag << " " << "urg: " << urg_flag; return buffer.str(); } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(syn_flag); ar& BOOST_SERIALIZATION_NVP(ack_flag); ar& BOOST_SERIALIZATION_NVP(fin_flag); ar& BOOST_SERIALIZATION_NVP(psh_flag); ar& BOOST_SERIALIZATION_NVP(rst_flag); ar& BOOST_SERIALIZATION_NVP(urg_flag); } }; bool operator==(const flow_spec_tcp_flagset_t& lhs, const flow_spec_tcp_flagset_t& rhs); bool operator!=(const flow_spec_tcp_flagset_t& lhs, const flow_spec_tcp_flagset_t& rhs); // All possible values for BGP Flow Spec fragmentation field enum class flow_spec_fragmentation_types_t { FLOW_SPEC_DONT_FRAGMENT, FLOW_SPEC_IS_A_FRAGMENT, FLOW_SPEC_FIRST_FRAGMENT, FLOW_SPEC_LAST_FRAGMENT, // Well, this entity does not exist in RFC at all. It was addition from ExaBGP FLOW_SPEC_NOT_A_FRAGMENT, }; // Flow spec actions enum class bgp_flow_spec_action_types_t { FLOW_SPEC_ACTION_DISCARD, FLOW_SPEC_ACTION_ACCEPT, FLOW_SPEC_ACTION_RATE_LIMIT, FLOW_SPEC_ACTION_REDIRECT, FLOW_SPEC_ACTION_MARK }; bool read_flow_spec_action_type_from_string(const std::string& string_form, bgp_flow_spec_action_types_t& action_type); std::string serialize_action_type(const bgp_flow_spec_action_types_t& action_type); class bgp_flow_spec_action_t { public: void set_type(bgp_flow_spec_action_types_t action_type) { this->action_type = action_type; } bgp_flow_spec_action_types_t get_type() const { return this->action_type; } void set_rate_limit(unsigned int rate_limit) { this->rate_limit = rate_limit; } unsigned int get_rate_limit() const { return this->rate_limit; } uint16_t get_redirect_as() const { return redirect_as; } uint32_t get_redirect_value() const { return redirect_value; } void set_redirect_as(uint16_t value) { redirect_as = value; } void set_redirect_value(uint32_t value) { redirect_value = value; } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(action_type); ar& BOOST_SERIALIZATION_NVP(rate_limit); ar& BOOST_SERIALIZATION_NVP(redirect_as); ar& BOOST_SERIALIZATION_NVP(redirect_value); } private: bgp_flow_spec_action_types_t action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_ACCEPT; unsigned int rate_limit = 0; // Values for redirect uint16_t redirect_as = 0; uint32_t redirect_value = 0; }; bool operator==(const bgp_flow_spec_action_t& lhs, const bgp_flow_spec_action_t& rhs); bool operator!=(const bgp_flow_spec_action_t& lhs, const bgp_flow_spec_action_t& rhs); // We do not use < and > operators at all, sorry class flow_spec_rule_t { public: // This operation is very heavy, it may crash in case of entropy shortage and it actually happened to our customer // And we must not do them in constructors as it causes lots of side effects and slows down all things bool generate_uuid() { boost::uuids::random_generator gen; try { announce_uuid = gen(); } catch (...) { return false; } return true; } void set_source_subnet_ipv4(const subnet_cidr_mask_t& source_subnet) { this->source_subnet_ipv4 = source_subnet; this->source_subnet_ipv4_used = true; } void set_source_subnet_ipv6(const subnet_ipv6_cidr_mask_t& source_subnet) { this->source_subnet_ipv6 = source_subnet; this->source_subnet_ipv6_used = true; } void set_destination_subnet_ipv4(const subnet_cidr_mask_t& destination_subnet) { this->destination_subnet_ipv4 = destination_subnet; this->destination_subnet_ipv4_used = true; } void set_destination_subnet_ipv6(const subnet_ipv6_cidr_mask_t& destination_subnet) { this->destination_subnet_ipv6 = destination_subnet; this->destination_subnet_ipv6_used = true; } void set_agent_subnet(subnet_cidr_mask_t subnet_param) { this->agent_subnet = subnet_param; this->agent_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_vlan(uint16_t vlan) { this->vlans.push_back(vlan); } void add_ipv4_nexthop(uint32_t ip) { this->ipv4_nexthops.push_back(ip); } void add_protocol(ip_protocol_t protocol) { this->protocols.push_back(protocol); } void add_ttl(uint8_t ttl) { this->ttls.push_back(ttl); } void add_fragmentation_flag(flow_spec_fragmentation_types_t flag) { this->fragmentation_flags.push_back(flag); } void add_tcp_flagset(flow_spec_tcp_flagset_t flag) { this->tcp_flags.push_back(flag); } void set_action(bgp_flow_spec_action_t action) { this->action = action; } bgp_flow_spec_action_t get_action() const { return this->action; } std::string get_announce_uuid_as_string() const { return boost::uuids::to_string(announce_uuid); } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(source_subnet_ipv4); ar& BOOST_SERIALIZATION_NVP(source_subnet_ipv4_used); ar& BOOST_SERIALIZATION_NVP(source_subnet_ipv6); ar& BOOST_SERIALIZATION_NVP(source_subnet_ipv6_used); ar& BOOST_SERIALIZATION_NVP(destination_subnet_ipv4); ar& BOOST_SERIALIZATION_NVP(destination_subnet_ipv4_used); ar& BOOST_SERIALIZATION_NVP(destination_subnet_ipv6); ar& BOOST_SERIALIZATION_NVP(destination_subnet_ipv6_used); ar& BOOST_SERIALIZATION_NVP(agent_subnet); ar& BOOST_SERIALIZATION_NVP(agent_subnet_used); ar& BOOST_SERIALIZATION_NVP(source_ports); ar& BOOST_SERIALIZATION_NVP(destination_ports); ar& BOOST_SERIALIZATION_NVP(packet_lengths); ar& BOOST_SERIALIZATION_NVP(vlans); ar& BOOST_SERIALIZATION_NVP(ttls); ar& BOOST_SERIALIZATION_NVP(ipv4_nexthops); ar& BOOST_SERIALIZATION_NVP(protocols); ar& BOOST_SERIALIZATION_NVP(fragmentation_flags); ar& BOOST_SERIALIZATION_NVP(tcp_flags); ar& BOOST_SERIALIZATION_NVP(set_match_bit_for_tcp_flags); ar& BOOST_SERIALIZATION_NVP(set_match_bit_for_fragmentation_flags); ar& BOOST_SERIALIZATION_NVP(action); ar& BOOST_SERIALIZATION_NVP(announce_uuid); } // Source prefix subnet_cidr_mask_t source_subnet_ipv4; bool source_subnet_ipv4_used = false; subnet_ipv6_cidr_mask_t source_subnet_ipv6; bool source_subnet_ipv6_used = false; // Destination prefix subnet_cidr_mask_t destination_subnet_ipv4; bool destination_subnet_ipv4_used = false; subnet_ipv6_cidr_mask_t destination_subnet_ipv6; bool destination_subnet_ipv6_used = false; // Agent subnet subnet_cidr_mask_t agent_subnet; bool agent_subnet_used = false; std::vector source_ports; std::vector destination_ports; // It's total IP packet length (excluding Layer 2 but including IP header) // https://datatracker.ietf.org/doc/html/rfc5575#section-4 std::vector packet_lengths; // This one is an non standard extension for our own purposes std::vector vlans; // This one is an non standard extension for our own purposes std::vector ttls; // IPv4 next hops for https://datatracker.ietf.org/doc/html/draft-ietf-idr-flowspec-redirect-ip-01 std::vector ipv4_nexthops ; std::vector protocols; std::vector fragmentation_flags; std::vector tcp_flags; // By default we do not use match bit for TCP flags when encode them to Flow Spec NLRI // But in some cases it could be really useful bool set_match_bit_for_tcp_flags = false; // By default we do not use match bit for fragmentation flags when encode them to Flow Spec NLRI // But in some cases (Huawei) it could be useful bool set_match_bit_for_fragmentation_flags = false; bgp_flow_spec_action_t action; boost::uuids::uuid announce_uuid{}; }; bool operator==(const flow_spec_rule_t& lhs, const flow_spec_rule_t& rhs); bool operator!=(const flow_spec_rule_t& lhs, const flow_spec_rule_t& rhs); bool read_flow_spec_from_json_to_native_format(const std::string& json_encoded_flow_spec, flow_spec_rule_t& flow_spec_rule, bool require_action); bool encode_flow_spec_to_json(const flow_spec_rule_t& flow_spec_rule, std::string& json_encoded_flow_spec, bool add_uuid); bool decode_native_flow_spec_announce_from_binary_encoded_atributes(std::vector binary_attributes, flow_spec_rule_t& flow_spec_rule); bool encode_bgp_flow_spec_action_as_extended_attribute(const bgp_flow_spec_action_t& bgp_flow_spec_action, dynamic_binary_buffer_t& extended_attributes_as_binary_array); // It's format of redirect target. So called route target community. Official spec RFC5575 is pretty vague about it: // https://datatracker.ietf.org/doc/html/rfc4360#section-4 // But new BGP Flow Spec clarifies it as https://datatracker.ietf.org/doc/html/rfc8955#name-rt-redirect-rt-redirect-sub class __attribute__((__packed__)) redirect_2_octet_as_4_octet_value_t { // We must not access these fields directly as it requires explicit byte order conversion private: uint16_t as = 0; uint32_t value = 0; public: uint16_t get_as_host_byte_order() const { return fast_ntoh(as); } uint32_t get_value_host_byte_order() const { return fast_ntoh(value); } std::string print() const { std::stringstream buffer; buffer << "as: " << get_as_host_byte_order() << " " << "value: " << get_value_host_byte_order() << " "; return buffer.str(); } }; static_assert(sizeof(redirect_2_octet_as_4_octet_value_t) == 6, "Bad size for redirect_2_octet_as_4_octet_value_t"); // More details at https://tools.ietf.org/html/rfc5575 page 6 class __attribute__((__packed__)) bgp_flow_spec_operator_byte_t { public: uint8_t equal : 1 = 0, greater_than : 1 = 0, less_than : 1 = 0, reserved : 1 = 0, bit_shift_len : 2 = 0, and_bit : 1 = 0, end_of_list : 1 = 0; void set_equal_bit() { equal = 1; } void set_greater_than_bit() { greater_than = 1; } void set_less_than_bit() { less_than = 1; } void set_and_bit() { and_bit = 1; } void set_end_of_list_bit() { end_of_list = 1; } bool set_length_in_bytes(uint32_t byte_length) { // We could set only for numbers which are pow of 2 if (byte_length == 1) { bit_shift_len = 0; } else if (byte_length == 2) { bit_shift_len = 1; } else if (byte_length == 4) { bit_shift_len = 2; } else { logger << log4cpp::Priority::ERROR << "Could not calculate log2 for " << byte_length; return false; } return true; } std::string print() const { std::stringstream buffer; buffer << "end of list: " << uint32_t(end_of_list) << " " << "and_bit: " << uint32_t(and_bit) << " " << "bit_shift_len: " << uint32_t(bit_shift_len) << " " << "reserved: " << uint32_t(reserved) << " " << "less_than: " << uint32_t(less_than) << " " << "greater_than: " << uint32_t(greater_than) << " " << "equal: " << uint32_t(equal); return buffer.str(); } // Real value evaluated as 1 << bit_shift_len uint32_t get_value_length() { return 1 << bit_shift_len; } }; // Here we store multiple enumerable values for flow spec protocol (ports, // protocols and other) class flow_spec_enumerable_lement { public: uint8_t one_byte_value = 0; uint16_t two_byte_value = 0; // Could be only 1 or 2 bytes uint32_t value_length = 0; bgp_flow_spec_operator_byte_t operator_byte{}; }; typedef std::vector multiple_flow_spec_enumerable_items_t; bool read_one_or_more_values_encoded_with_operator_byte(uint8_t* start, uint8_t* global_end, uint32_t& readed_bytes, multiple_flow_spec_enumerable_items_t& multiple_flow_spec_enumerable_items); std::string get_flow_spec_type_name_by_number(uint8_t flow_spec_type); std::string get_bgp_attribute_name_by_number(uint8_t bgp_attribute_type); bool flow_spec_decode_nlri_value(uint8_t* data_ptr, uint32_t data_length, flow_spec_rule_t& flow_spec_rule); class __attribute__((__packed__)) bgp_flow_spec_fragmentation_entity_t { public: uint8_t dont_fragment : 1 = 0, is_fragment : 1 = 0, first_fragment : 1 = 0, last_fragment : 1 = 0, reserved : 4 = 0; std::string print() const { std::stringstream buffer; buffer << "reserved: " << uint32_t(reserved) << " " << "last_fragment: " << uint32_t(last_fragment) << " " << "first_fragment: " << uint32_t(first_fragment) << " " << "is_fragment: " << uint32_t(is_fragment) << " " << "dont_fragment: " << uint32_t(dont_fragment); return buffer.str(); } }; static_assert(sizeof(bgp_flow_spec_fragmentation_entity_t) == 1, "Broken size for bgp_flow_spec_fragmentation_entity_t"); // More details at https://tools.ietf.org/html/rfc5575#page-9 // We use this version of operator byte for TCP flags and for fragmentation flags class __attribute__((__packed__)) bgp_flow_spec_bitmask_operator_byte_t { public: uint8_t match_bit : 1 = 0, not_bit : 1 = 0, reserved2 : 1 = 0, reserved1 : 1 = 0, bit_shift_len : 2 = 0, and_bit : 1 = 0, end_of_list : 1 = 0; bgp_flow_spec_bitmask_operator_byte_t() { memset(this, 0, sizeof(*this)); } std::string print() const { std::stringstream buffer; buffer << "end of list: " << uint32_t(end_of_list) << " " << "and_bit: " << uint32_t(and_bit) << " " << "bit_shift_len: " << uint32_t(bit_shift_len) << " " << "reserved1: " << uint32_t(reserved1) << " " << "reserved2: " << uint32_t(reserved2) << " " << "not_bit: " << uint32_t(not_bit) << " " << "match_bit: " << uint32_t(match_bit); return buffer.str(); } void set_not_bit() { not_bit = 1; } void set_and_bit() { and_bit = 1; } void set_match_bit() { match_bit = 1; } void set_end_of_list_bit() { end_of_list = 1; } bool set_length_in_bytes(uint32_t byte_length) { // We could set only for numbers which are pow of 2 if (byte_length == 1) { bit_shift_len = 0; } else if (byte_length == 2) { bit_shift_len = 1; } else if (byte_length == 4) { bit_shift_len = 2; } else { logger << log4cpp::Priority::WARN << "Could not calculate log2 for " << byte_length; return false; } return true; } // Real value evaluated as 1 << bit_shift_len uint32_t get_value_length() { return 1 << bit_shift_len; } }; // We have two ways to encode TCP flags - one byte and two byte // This is extracted some piece of code from: tcp_header_t / // network_data_structures class __attribute__((__packed__)) bgp_flowspec_two_byte_encoded_tcp_flags_t { public: uint16_t fin : 1 = 0, syn : 1 = 0, rst : 1 = 0, psh : 1 = 0, ack : 1 = 0, urg : 1 = 0, ece : 1 = 0, cwr : 1 = 0, ns : 1 = 0, reserved : 3 = 0, data_offset : 4 = 0; }; static_assert(sizeof(bgp_flowspec_two_byte_encoded_tcp_flags_t) == 2, "Bad size for bgp_flowspec_two_byte_encoded_tcp_flags_t"); class __attribute__((__packed__)) bgp_flowspec_one_byte_byte_encoded_tcp_flags_t { public: // Just drop 8 bytes from bgp_flowspec_two_byte_encoded_tcp_flags uint8_t fin : 1 = 0, syn : 1 = 0, rst : 1 = 0, psh : 1 = 0, ack : 1 = 0, urg : 1 = 0, ece : 1 = 0, cwr : 1 = 0; std::string print() const { std::stringstream buffer; buffer << "cwr: " << uint32_t(cwr) << " " << "ece: " << uint32_t(ece) << " " << "urg: " << uint32_t(urg) << " " << "ack: " << uint32_t(ack) << " " << "psh: " << uint32_t(psh) << " " << "rst: " << uint32_t(rst) << " " << "syn: " << uint32_t(syn) << " " << "fin: " << uint32_t(fin); return buffer.str(); } }; static_assert(sizeof(bgp_flowspec_one_byte_byte_encoded_tcp_flags_t) == 1, "Bad size for "); // BGP flow spec entity numbers enum FLOW_SPEC_ENTITY_TYPES : uint8_t { FLOW_SPEC_ENTITY_DESTINATION_PREFIX = 1, FLOW_SPEC_ENTITY_SOURCE_PREFIX = 2, FLOW_SPEC_ENTITY_IP_PROTOCOL = 3, FLOW_SPEC_ENTITY_PORT = 4, FLOW_SPEC_ENTITY_DESTINATION_PORT = 5, FLOW_SPEC_ENTITY_SOURCE_PORT = 6, FLOW_SPEC_ENTITY_ICMP_TYPE = 7, FLOW_SPEC_ENTITY_ICMP_CODE = 8, FLOW_SPEC_ENTITY_TCP_FLAGS = 9, FLOW_SPEC_ENTITY_PACKET_LENGTH = 10, FLOW_SPEC_ENTITY_DSCP = 11, FLOW_SPEC_ENTITY_FRAGMENT = 12, }; /* Here we have custom NLRI encoding (https://tools.ietf.org/html/rfc4760#section-5.1.3): +---------------------------------------------------------+ | Address Family Identifier (2 octets) | +---------------------------------------------------------+ | Subsequent Address Family Identifier (1 octet) | +---------------------------------------------------------+ | Length of Next Hop Network Address (1 octet) | +---------------------------------------------------------+ | Network Address of Next Hop (variable) | +---------------------------------------------------------+ | Reserved (1 octet) | +---------------------------------------------------------+ | Network Layer Reachability Information (variable) | +---------------------------------------------------------+ */ class __attribute__((__packed__)) bgp_mp_ext_flow_spec_header_t { public: uint16_t afi_identifier = AFI_IP; uint8_t safi_identifier = SAFI_FLOW_SPEC_UNICAST; // For BGP Flow spec we are using blank next hop because it's useless for us // now uint8_t length_of_next_hop = 0; // Here we have blank next hop. Or haven't ... :) uint8_t reserved = 0; // Here we have NLRI information void network_to_host_byte_order() { afi_identifier = ntohs(afi_identifier); } void host_byte_order_to_network_byte_order() { afi_identifier = htons(afi_identifier); } std::string print() const { std::stringstream buffer; buffer << "afi_identifier: " << uint32_t(afi_identifier) << " " << "safi_identifier: " << uint32_t(safi_identifier) << " " << "length_of_next_hop: " << uint32_t(length_of_next_hop) << " " << "reserved: " << uint32_t(reserved); return buffer.str(); } }; class __attribute__((__packed__)) bgp_extended_community_element_flow_spec_rate_t { public: uint8_t type_hight = EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL; uint8_t type_low = FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_RATE; // This bytes are meaningless and should not processed at all by receiver side uint8_t value[2] = { 0, 0 }; float rate_limit = 0; void host_byte_order_to_network_byte_order() { // Have you ever do little endian to big endian conversion for float? We do! float rate_limit_copy = rate_limit; logger << log4cpp::Priority::DEBUG << "Original rate: " << rate_limit; // We do not use pointer to field structure here because it may cause alignment issues and gcc yells on it: // warning: taking address of packed member of ... may result in an unaligned pointer value [-Waddress-of-packed-member] uint32_t* integer_pointer = (uint32_t*)&rate_limit_copy; logger << log4cpp::Priority::DEBUG << "Integer part of rate: " << *integer_pointer; *integer_pointer = htonl(*integer_pointer); // Overwrite original value this->rate_limit = rate_limit_copy; logger << log4cpp::Priority::DEBUG << "Network byte order encoded rate limit: " << rate_limit; } std::string print() const { std::stringstream buffer; buffer << "type hight: " << uint32_t(type_hight) << " " << "type low: " << uint32_t(type_low) << " " << "value raw: " << print_binary_string_as_hex_with_leading_0x(value, sizeof(value)); return buffer.str(); } }; static_assert(sizeof(bgp_extended_community_element_flow_spec_rate_t) == 8, "Bad size for bgp_extended_community_element_flow_spec_rate_t"); class __attribute__((__packed__)) bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value_t_t { public: uint8_t type_hight = EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL; uint8_t type_low = FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_REDIRECT_AS_TWO_BYTE; // 6 octet value uint16_t redirect_as = 0; uint32_t redirect_value = 0; void set_redirect_as(uint16_t value) { redirect_as = fast_hton(value); } void set_redirect_value(uint32_t value) { redirect_value = fast_hton(value); } std::string print() const { std::stringstream buffer; buffer << "type hight: " << uint32_t(type_hight) << " " << "type low: " << uint32_t(type_low) << " " << "redirect_as: " << redirect_as << " " << "redirect_value: " << redirect_value; return buffer.str(); } }; static_assert(sizeof(bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value_t_t) == 8, "Bad size for bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value_t_t"); // This structure encodes Flow Spec next hop IPv4 class __attribute__((__packed__)) bgp_extended_community_element_flow_spec_ipv4_next_hop_t { public: uint8_t type_hight = EXTENDED_COMMUNITY_TRANSITIVE_IPV4_ADDRESS_SPECIFIC; uint8_t type_low = BGP_IPV4_EXTENDED_COMMUNITY_SUBTYPE_FLOW_SPEC_REDIRECT_IPv4; // Actual value of IPv4 next hop uint32_t next_hop_ipv4 = 0; // In this field we can set mirror flag to make packet copies uint16_t local_administrator = 0; void host_byte_order_to_network_byte_order() { } std::string print() const { std::stringstream buffer; buffer << "type hight: " << uint32_t(type_hight) << " " << "type low: " << uint32_t(type_low) << " " << "nexthop: " << next_hop_ipv4 << " " << "local administrator: " << local_administrator; return buffer.str(); } }; static_assert(sizeof(bgp_extended_community_element_flow_spec_ipv4_next_hop_t) == 8, "Bad size for bgp_extended_community_element_flow_spec_ipv4_next_hop_t"); static_assert(sizeof(bgp_flow_spec_bitmask_operator_byte_t) == 1, "Bad size for bgp_flow_spec_bitmask_operator_byte_t"); static_assert(sizeof(bgp_flow_spec_operator_byte_t) == 1, "Bad size for bgp_flow_spec_operator_byte_t"); bool read_flow_spec_tcp_flags_from_strig(const std::string& string_form, flow_spec_tcp_flagset_t& tcp_flagset); bool read_flow_spec_fragmentation_types_from_string(const std::string& string_form, flow_spec_fragmentation_types_t& fragment_flag); bool valid_port(int32_t port); bool encode_flow_spec_to_json_raw(const flow_spec_rule_t& flow_spec_rule, bool add_uuid, nlohmann::json& flow_json); std::string flow_spec_fragmentation_flags_to_string(flow_spec_fragmentation_types_t const& fragment_flag); std::string flow_spec_tcp_flagset_to_string(flow_spec_tcp_flagset_t const& tcp_flagset); void uint8t_representation_of_tcp_flags_to_flow_spec(uint8_t tcp_flags, flow_spec_tcp_flagset_t& flagset); fastnetmon-1.2.8/src/conanfile.txt000066400000000000000000000005371472727706000172070ustar00rootroot00000000000000[requires] openssl/1.1.1s capnproto/0.10.3 boost/1.81.0 grpc/1.50.1 # we use previous version to avoid dependency conflict with gGRP protobuf/3.21.4 librdkafka/2.0.2 cppkafka/0.4.0 # it was linked against older openssl/1.1.1t and it causes build issues, we have to disable it for now #mongo-c-driver/1.23.2 hiredis/1.1.0 [generators] CMakeToolchain fastnetmon-1.2.8/src/dynamic_binary_buffer.hpp000066400000000000000000000134131472727706000215370ustar00rootroot00000000000000#pragma once #include class dynamic_binary_buffer_t { public: dynamic_binary_buffer_t() : byte_storage(nullptr), maximum_internal_storage_size(0) { // std::cout << "Default constructor called" << std::endl; } // Explicitly removed it as we need to implement it properly when needed dynamic_binary_buffer_t(dynamic_binary_buffer_t&& that) = delete; // We should set maximum buffer size here. // TODO: add ability to relocate memory of we need more memory bool set_maximum_buffer_size_in_bytes(ssize_t size) { // Already allocated if (byte_storage) { return false; } // With nothrow we are using new without exceptions byte_storage = new (std::nothrow) uint8_t[size]; if (byte_storage) { maximum_internal_storage_size = size; return true; } else { return false; } } ~dynamic_binary_buffer_t() { // std::cout << "Destructor called" << std::endl; if (byte_storage) { delete[] byte_storage; byte_storage = nullptr; maximum_internal_storage_size = 0; } } // So this implementation will be useful only for real object copies // For returning local variable from function compiler will do this job // perfectly: // https://en.wikipedia.org/wiki/Return_value_optimization dynamic_binary_buffer_t(const dynamic_binary_buffer_t& that) { this->maximum_internal_storage_size = that.maximum_internal_storage_size; // Copy internal pointer too! It's very important! this->internal_data_shift = that.internal_data_shift; // std::cout << "Copy constructor called" << std::endl; // std::cout << "Copy constructor will copy " << this->internal_size << " // bytes" << // std::endl; // We are copying all memory (unused too) if (this->maximum_internal_storage_size > 0) { // Allocate memory for new instance this->set_maximum_buffer_size_in_bytes(this->maximum_internal_storage_size); memcpy(this->byte_storage, that.byte_storage, that.maximum_internal_storage_size); } } // All this functions just append some data with certain length to buffer and // increase total // size // They are very similar to std::stringstream but for binary data only bool append_byte(uint8_t byte_value) { // Do bounds check if (internal_data_shift > maximum_internal_storage_size - 1) { errors_occured = true; return false; } byte_storage[internal_data_shift] = byte_value; internal_data_shift += sizeof(uint8_t); return true; } // Use reference as argument bool append_dynamic_buffer(dynamic_binary_buffer_t& dynamic_binary_buffer) { // In this case we are copying only used memory // TODO: Why +1? if (internal_data_shift + dynamic_binary_buffer.get_used_size() > maximum_internal_storage_size + 1) { errors_occured = true; return false; } return this->append_data_as_pointer(dynamic_binary_buffer.get_pointer(), dynamic_binary_buffer.get_used_size()); } bool append_data_as_pointer(const void* ptr, size_t length) { if (internal_data_shift + length > maximum_internal_storage_size + 1) { errors_occured = true; return false; } memcpy(byte_storage + internal_data_shift, ptr, length); internal_data_shift += length; return true; } template bool append_data_as_object_ptr(src_type* ptr) { if (internal_data_shift + sizeof(src_type) > maximum_internal_storage_size + 1) { errors_occured = true; return false; } memcpy(byte_storage + internal_data_shift, ptr, sizeof(src_type)); internal_data_shift += sizeof(src_type); return true; } // All functions below DO NOT CHANGE internal buffer position! They are very // low level and // should be avoided! // We could set arbitrary byte with this function bool set_byte(uint32_t byte_number, uint8_t byte_value) { // Do bounds check if (byte_number > maximum_internal_storage_size - 1) { errors_occured = true; return false; } byte_storage[byte_number] = byte_value; return true; } bool memcpy_from_ptr(uint32_t shift, const void* ptr, uint32_t length) { if (shift + length > maximum_internal_storage_size + 1) { errors_occured = true; return false; } memcpy(byte_storage + shift, ptr, length); return true; } // More user friendly version of previous function template bool memcpy_from_object_ptr(uint32_t shift, src_type* ptr) { if (shift + sizeof(src_type) > maximum_internal_storage_size + 1) { errors_occured = true; return false; } memcpy(byte_storage + shift, ptr, sizeof(src_type)); return true; } // Return full size (with non initialized data region too) uint32_t get_full_size() { return maximum_internal_storage_size; } // Return only used memory region size_t get_used_size() { return internal_data_shift; } const uint8_t* get_pointer() { return byte_storage; } // If we have any issues with it bool is_failed() { return errors_occured; } private: size_t internal_data_shift = 0; uint8_t* byte_storage = nullptr; ssize_t maximum_internal_storage_size = 0; // If any errors occurred in any time when we used this buffer bool errors_occured = false; }; fastnetmon-1.2.8/src/example_plugin/000077500000000000000000000000001472727706000175145ustar00rootroot00000000000000fastnetmon-1.2.8/src/example_plugin/example_collector.cpp000066400000000000000000000035121472727706000237220ustar00rootroot00000000000000#include "../all_logcpp_libraries.hpp" // For config map operations #include #include #include "../iana_ip_protocols.hpp" #include "example_collector.hpp" // Get log4cpp logger from main program 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_t 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 = IpProtocolNumberICMP; /* TCP */ current_packet.protocol = IpProtocolNumberTCP; current_packet.source_port = 0; current_packet.destination_port = 0; /* UDP */ current_packet.protocol = IpProtocolNumberUDP; current_packet.source_port = 0; current_packet.destination_port = 0; example_process_func_ptr(current_packet); } fastnetmon-1.2.8/src/example_plugin/example_collector.hpp000066400000000000000000000003201472727706000237210ustar00rootroot00000000000000#ifndef EXAMPLE_PLUGIN_H #define EXAMPLE_PLUGIN_H #include "../fastnetmon_types.hpp" // This function should be implemented in plugin void start_example_collection(process_packet_pointer func_ptr); #endif fastnetmon-1.2.8/src/fast_endianless.hpp000066400000000000000000000034421472727706000203610ustar00rootroot00000000000000#pragma once #include #ifdef _WIN32 #include #else #include #endif // 64 bit endian-less transformation functions are platform specific #ifdef __APPLE__ #include #define be64toh(x) OSSwapBigToHostInt64(x) #define htobe64(x) OSSwapHostToBigInt64(x) #elif _WIN32 #define be64toh(x) _byteswap_uint64(x) #define htobe64(x) _byteswap_uint64(x) #endif // We need this include for be64toh and htobe64 on FreeBSD platforms #if defined(__FreeBSD__) || defined(__DragonFly__) #include #endif // Linux standard functions for endian conversions are ugly because there are no checks about arguments length // And you could accidentally use ntohs (suitable only for 16 bit) for 32 or 64 bit value and nobody will warning you // With this wrapper functions it's pretty complicated to use them for incorrect length type! :) // Type safe versions of ntohl, ntohs with type control inline uint16_t fast_ntoh(uint16_t value) { return ntohs(value); } inline uint32_t fast_ntoh(uint32_t value) { return ntohl(value); } inline int32_t fast_ntoh(int32_t value) { return ntohl(value); } // network (big endian) byte order to host byte order inline uint64_t fast_ntoh(uint64_t value) { return be64toh(value); } // Type safe version of htonl, htons inline uint16_t fast_hton(uint16_t value) { return htons(value); } inline uint32_t fast_hton(uint32_t value) { return htonl(value); } inline int32_t fast_hton(int32_t value) { return htonl(value); } inline uint64_t fast_hton(uint64_t value) { // host to big endian (network byte order) return htobe64(value); } // Explicitly remove all other types to avoid implicit conversion template void fast_ntoh(T) = delete; template void fast_hton(T) = delete; fastnetmon-1.2.8/src/fast_library.cpp000066400000000000000000002426271472727706000177050ustar00rootroot00000000000000#include "fast_library.hpp" #include #include // Windows does not use ioctl #ifndef _WIN32 #include #endif #ifndef _WIN32 // For uname function #include #endif #include "all_logcpp_libraries.hpp" #include #include #include #include #include #include #include #include #ifdef ENABLE_CAPNP #include "simple_packet_capnp/simple_packet.capnp.h" #include #include #endif #include #include #include "iana_ip_protocols.hpp" 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()); } 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); } std::string convert_ipv4_subnet_to_string(const subnet_cidr_mask_t& subnet) { std::stringstream buffer; buffer << convert_ip_as_uint_to_string(subnet.subnet_address) << "/" << subnet.cidr_prefix_length; return buffer.str(); } // convert integer to string std::string convert_int_to_string(int value) { std::stringstream out; out << value; return out.str(); } // Converts IP address in cidr form 11.22.33.44/24 to our representation bool convert_subnet_from_string_to_binary_with_cidr_format_safe(const std::string& subnet_cidr, subnet_cidr_mask_t& subnet_cidr_mask) { if (subnet_cidr.empty()) { return false; } // It's not a cidr mask if (!is_cidr_subnet(subnet_cidr)) { return false; } std::vector subnet_as_string; split(subnet_as_string, subnet_cidr, boost::is_any_of("/"), boost::token_compress_on); if (subnet_as_string.size() != 2) { return false; } uint32_t subnet_as_int = 0; bool ip_to_integer_convresion_result = convert_ip_as_string_to_uint_safe(subnet_as_string[0], subnet_as_int); if (!ip_to_integer_convresion_result) { return false; } int cidr = 0; bool ip_conversion_result = convert_string_to_any_integer_safe(subnet_as_string[1], cidr); if (!ip_conversion_result) { return false; } subnet_cidr_mask = subnet_cidr_mask_t(subnet_as_int, cidr); return true; } std::string convert_subnet_to_string(subnet_cidr_mask_t my_subnet) { std::stringstream buffer; buffer << convert_ip_as_uint_to_string(my_subnet.subnet_address) << "/" << my_subnet.cidr_prefix_length; return buffer.str(); } // extract 24 from 192.168.1.1/24 unsigned int get_cidr_mask_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 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 std::string(); } 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) { // We can do bit shift only for 0 .. 31 bits but we cannot do it in case of 32 bits // Shift for same number of bits as type has is undefined behaviour in C standard: // https://stackoverflow.com/questions/7401888/why-doesnt-left-bit-shift-for-32-bit-integers-work-as-expected-when-used // We will handle this case manually if (cidr == 0) { return 0; } uint32_t binary_netmask = 0xFFFFFFFF; binary_netmask = binary_netmask << (32 - cidr); // We need network byte order at output return htonl(binary_netmask); } bool is_cidr_subnet(std::string subnet) { boost::cmatch what; return regex_match(subnet.c_str(), what, regular_expression_cidr_pattern); } 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; } // 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 "-"; } /* // 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; } } // Encodes simple packet with all fields as separate fields in json format bool serialize_simple_packet_to_json(const simple_packet_t& packet, nlohmann::json& json_packet) { extern log4cpp::Category& logger; std::string protocol_version; std::string source_ip_as_string; std::string destination_ip_as_string; if (packet.ip_protocol_version == 4) { protocol_version = "ipv4"; 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) { protocol_version = "ipv6"; source_ip_as_string = print_ipv6_address(packet.src_ipv6); destination_ip_as_string = print_ipv6_address(packet.dst_ipv6); } else { protocol_version = "unknown"; } try { // We use arrival_time as traffic telemetry protocols do not provide this time in a reliable manner json_packet["timestamp"] = packet.arrival_time; json_packet["ip_version"] = protocol_version; json_packet["source_ip"] = source_ip_as_string; json_packet["destination_ip"] = destination_ip_as_string; json_packet["source_asn"] = packet.src_asn; json_packet["destination_asn"] = packet.dst_asn; json_packet["source_country"] = country_static_string_to_dynamic_string(packet.src_country); json_packet["destination_country"] = country_static_string_to_dynamic_string(packet.dst_country); json_packet["input_interface"] = packet.input_interface; json_packet["output_interface"] = packet.output_interface; // Add ports for TCP and UDP if (packet.protocol == IPPROTO_TCP or packet.protocol == IPPROTO_UDP) { json_packet["source_port"] = packet.source_port; json_packet["destination_port"] = packet.destination_port; } // Add agent information std::string agent_ip_as_string = convert_ip_as_uint_to_string(packet.agent_ip_address); json_packet["agent_address"] = agent_ip_as_string; if (packet.protocol == IPPROTO_TCP) { std::string tcp_flags = print_tcp_flags(packet.flags); json_packet["tcp_flags"] = tcp_flags; } // Add forwarding status std::string forwarding_status = forwarding_status_to_string(packet.forwarding_status); json_packet["forwarding_status"] = forwarding_status; json_packet["fragmentation"] = packet.ip_fragmented; json_packet["packets"] = packet.number_of_packets; json_packet["length"] = packet.length; json_packet["ip_length"] = packet.ip_length; json_packet["ttl"] = packet.ttl; json_packet["sample_ratio"] = packet.sample_ratio; std::string protocol = get_printable_protocol_name(packet.protocol); json_packet["protocol"] = protocol; } catch (...) { logger << log4cpp::Priority::ERROR << "Exception was triggered in JSON logic in serialize_simple_packet_to_json"; return false; } return true; } std::string print_simple_packet(simple_packet_t packet) { std::stringstream buffer; if (packet.ts.tv_sec == 0) { // Netmap does 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? } std::string protocol_name = get_ip_protocol_name_by_number_iana(packet.protocol); // We use lowercase format boost::algorithm::to_lower(protocol_name); buffer << source_ip_as_string << ":" << packet.source_port << " > " << destination_ip_as_string << ":" << packet.destination_port << " protocol: " << protocol_name; // 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(const timeval& tv) { time_t nowtime = tv.tv_sec; tm* nowtm = localtime(&nowtime); std::ostringstream ss; ss << std::put_time(nowtm, "%F %H:%M:%S"); // Add microseconds // If value is short we will add leading zeros ss << "." << std::setfill('0') << std::setw(6) << tv.tv_usec; return ss.str(); } uint64_t convert_speed_to_mbps(uint64_t speed_in_bps) { return uint64_t((double)speed_in_bps / 1000 / 1000 * 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 and capture output bool exec(const std::string& cmd, std::vector& output_list, std::string& error_text) { FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) { // We need more details in case of failure error_text = "error code: " + std::to_string(errno) + " error text: " + strerror(errno); return false; } 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 true; } 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) { // Do not bother Graphite if we do not have any metrics here if (graphite_data.size() == 0) { return true; } 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::string error_text; std::vector output_list; bool exec_result = exec("ip -o link show", output_list, error_text); if (!exec_result) { return interfaces_list; } 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; bool exec_result = exec("ip address show dev " + interface_name, output_list, error_text); if (!exec_result) { return ip_list; } 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); } // It could not be on start or end of the line boost::regex ipv6_address_compression_algorithm("(0000:){2,}"); // Returns true when all octets of IP address are set to zero bool is_zero_ipv6_address(const in6_addr& ipv6_address) { const uint8_t* b = ipv6_address.s6_addr; if (b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0 && b[4] == 0 && b[5] == 0 && b[6] == 0 && b[7] == 0 && b[8] == 0 && b[9] == 0 && b[10] == 0 && b[11] == 0 && b[12] == 0 && b[13] == 0 && b[14] == 0 && b[15] == 0) { return true; } return false; } std::string print_ipv6_address(const in6_addr& ipv6_address) { char buffer[128]; // For short print const 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_t get_packet_direction_ipv6(patricia_tree_t* lookup_tree, struct in6_addr src_ipv6, struct in6_addr dst_ipv6, subnet_ipv6_cidr_mask_t& subnet) { direction_t 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); subnet_ipv6_cidr_mask_t destination_subnet; if (found_patrica_node) { our_ip_is_destination = true; destination_subnet.subnet_address = found_patrica_node->prefix->add.sin6; destination_subnet.cidr_prefix_length = found_patrica_node->prefix->bitlen; } found_patrica_node = NULL; prefix_for_check_address.add.sin6 = src_ipv6; subnet_ipv6_cidr_mask_t source_subnet; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_address, 1); if (found_patrica_node) { our_ip_is_source = true; source_subnet.subnet_address = found_patrica_node->prefix->add.sin6; source_subnet.cidr_prefix_length = found_patrica_node->prefix->bitlen; } if (our_ip_is_source && our_ip_is_destination) { packet_direction = INTERNAL; } else if (our_ip_is_source) { subnet = source_subnet; packet_direction = OUTGOING; } else if (our_ip_is_destination) { subnet = destination_subnet; packet_direction = INCOMING; } else { packet_direction = OTHER; } return packet_direction; } /* Get traffic type: check it belongs to our IPs */ direction_t get_packet_direction(patricia_tree_t* lookup_tree, uint32_t src_ip, uint32_t dst_ip, subnet_cidr_mask_t& subnet) { direction_t 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; subnet_cidr_mask_t destination_subnet; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); if (found_patrica_node) { our_ip_is_destination = true; destination_subnet.subnet_address = found_patrica_node->prefix->add.sin.s_addr; destination_subnet.cidr_prefix_length = found_patrica_node->prefix->bitlen; } found_patrica_node = NULL; prefix_for_check_adreess.add.sin.s_addr = src_ip; subnet_cidr_mask_t source_subnet; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); if (found_patrica_node) { our_ip_is_source = true; source_subnet.subnet_address = found_patrica_node->prefix->add.sin.s_addr; source_subnet.cidr_prefix_length = found_patrica_node->prefix->bitlen; } if (our_ip_is_source && our_ip_is_destination) { packet_direction = INTERNAL; } else if (our_ip_is_source) { subnet = source_subnet; packet_direction = OUTGOING; } else if (our_ip_is_destination) { subnet = destination_subnet; packet_direction = INCOMING; } else { packet_direction = OTHER; } return packet_direction; } std::string get_direction_name(direction_t 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; } #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 std::string serialize_attack_description(const attack_details_t& 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.traffic_counters.total.in_bytes) << " mbps\n" << "Total outgoing traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.total.out_bytes) << " mbps\n" << "Total incoming pps: " << current_attack.traffic_counters.total.in_packets << " packets per second\n" << "Total outgoing pps: " << current_attack.traffic_counters.total.out_packets << " packets per second\n" << "Total incoming flows: " << current_attack.traffic_counters.in_flows << " flows per second\n" << "Total outgoing flows: " << current_attack.traffic_counters.out_flows << " flows per second\n"; attack_description << "Incoming ip fragmented traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.fragmented.in_bytes) << " mbps\n" << "Outgoing ip fragmented traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.fragmented.out_bytes) << " mbps\n" << "Incoming ip fragmented pps: " << current_attack.traffic_counters.fragmented.in_packets << " packets per second\n" << "Outgoing ip fragmented pps: " << current_attack.traffic_counters.fragmented.out_packets << " packets per second\n" << "Incoming dropped traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.dropped.in_bytes) << " mbps\n" << "Outgoing dropped traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.dropped.out_bytes) << " mbps\n" << "Incoming dropped pps: " << current_attack.traffic_counters.dropped.in_packets << " packets per second\n" << "Outgoing dropped pps: " << current_attack.traffic_counters.dropped.out_packets << " packets per second\n" << "Incoming tcp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.tcp.in_bytes) << " mbps\n" << "Outgoing tcp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.tcp.out_bytes) << " mbps\n" << "Incoming tcp pps: " << current_attack.traffic_counters.tcp.in_packets << " packets per second\n" << "Outgoing tcp pps: " << current_attack.traffic_counters.tcp.out_packets << " packets per second\n" << "Incoming syn tcp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.tcp_syn.in_bytes) << " mbps\n" << "Outgoing syn tcp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.tcp_syn.out_bytes) << " mbps\n" << "Incoming syn tcp pps: " << current_attack.traffic_counters.tcp_syn.in_packets << " packets per second\n" << "Outgoing syn tcp pps: " << current_attack.traffic_counters.tcp_syn.out_packets << " packets per second\n" << "Incoming udp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.udp.in_bytes) << " mbps\n" << "Outgoing udp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.udp.out_bytes) << " mbps\n" << "Incoming udp pps: " << current_attack.traffic_counters.udp.in_packets << " packets per second\n" << "Outgoing udp pps: " << current_attack.traffic_counters.udp.out_packets << " packets per second\n" << "Incoming icmp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.icmp.in_bytes) << " mbps\n" << "Outgoing icmp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.icmp.out_bytes) << " mbps\n" << "Incoming icmp pps: " << current_attack.traffic_counters.icmp.in_packets << " packets per second\n" << "Outgoing icmp pps: " << current_attack.traffic_counters.icmp.out_packets << " packets per second\n"; return attack_description.str(); } attack_type_t detect_attack_type(const attack_details_t& current_attack) { double threshold_value = 0.9; if (current_attack.attack_direction == INCOMING) { if (current_attack.traffic_counters.tcp_syn.in_packets > threshold_value * current_attack.traffic_counters.total.in_packets) { return ATTACK_SYN_FLOOD; } else if (current_attack.traffic_counters.icmp.in_packets > threshold_value * current_attack.traffic_counters.total.in_packets) { return ATTACK_ICMP_FLOOD; } else if (current_attack.traffic_counters.fragmented.in_packets > threshold_value * current_attack.traffic_counters.total.in_packets) { return ATTACK_IP_FRAGMENTATION_FLOOD; } else if (current_attack.traffic_counters.udp.in_packets > threshold_value * current_attack.traffic_counters.total.in_packets) { return ATTACK_UDP_FLOOD; } } else if (current_attack.attack_direction == OUTGOING) { if (current_attack.traffic_counters.tcp_syn.out_packets > threshold_value * current_attack.traffic_counters.total.out_packets) { return ATTACK_SYN_FLOOD; } else if (current_attack.traffic_counters.icmp.out_packets > threshold_value * current_attack.traffic_counters.total.out_packets) { return ATTACK_ICMP_FLOOD; } else if (current_attack.traffic_counters.fragmented.out_packets > threshold_value * current_attack.traffic_counters.total.out_packets) { return ATTACK_IP_FRAGMENTATION_FLOOD; } else if (current_attack.traffic_counters.udp.out_packets > threshold_value * current_attack.traffic_counters.total.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(subnet_counter_t& 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.total.in_bytes) << " mbps\n" << prefix << " outgoing traffic: " << convert_speed_to_mbps(network_speed_meter.total.out_bytes) << " mbps\n" << prefix << " incoming pps: " << network_speed_meter.total.in_packets << " packets per second\n" << prefix << " outgoing pps: " << network_speed_meter.total.out_packets << " packets per second\n"; return buffer.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; } } bool convert_hex_as_string_to_uint(std::string hex, uint32_t& value) { std::stringstream ss; ss << std::hex << hex; ss >> value; return ss.fail(); } #ifdef __linux__ // We use this logic only from AF_PACKET and we clearly have no reasons to maintain cross platform portability for it // Get interface number by name bool get_interface_number_by_device_name(int socket_fd, std::string interface_name, int& interface_number) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); if (interface_name.size() > IFNAMSIZ) { return false; } strncpy(ifr.ifr_name, interface_name.c_str(), sizeof(ifr.ifr_name)); if (ioctl(socket_fd, SIOCGIFINDEX, &ifr) == -1) { return false; } interface_number = ifr.ifr_ifindex; return true; } #endif #if defined(__APPLE__) || defined(_WIN32) bool set_boost_process_name(boost::thread* thread, const std::string& process_name) { extern log4cpp::Category& logger; logger << log4cpp::Priority::ERROR << "We do not support custom thread names on this platform"; return false; } #else bool set_boost_process_name(boost::thread* thread, const std::string& process_name) { extern log4cpp::Category& logger; if (process_name.size() > 15) { logger << log4cpp::Priority::ERROR << "Process name should not exceed 15 symbols " << process_name; return false; } // The buffer specified by name should be at least 16 characters in length. char new_process_name[16]; strcpy(new_process_name, process_name.c_str()); int result = pthread_setname_np(thread->native_handle(), new_process_name); if (result != 0) { logger << log4cpp::Priority::ERROR << "pthread_setname_np failed with code: " << result; logger << log4cpp::Priority::ERROR << "Failed to set process name for " << process_name; } return true; } #endif #ifdef ENABLE_CAPNP bool read_simple_packet(uint8_t* buffer, size_t buffer_length, simple_packet_t& packet) { extern log4cpp::Category& logger; try { auto words = kj::heapArray(buffer_length / sizeof(capnp::word)); memcpy(words.begin(), buffer, words.asBytes().size()); capnp::FlatArrayMessageReader reader(words); auto root = reader.getRoot(); packet.protocol = root.getProtocol(); packet.sample_ratio = root.getSampleRatio(); packet.src_ip = root.getSrcIp(); packet.dst_ip = root.getDstIp(); packet.ip_protocol_version = root.getIpProtocolVersion(); packet.src_asn = root.getSrcAsn(); packet.dst_asn = root.getDstAsn(); packet.input_interface = root.getInputInterface(); packet.output_interface = root.getOutputInterface(); packet.agent_ip_address = root.getAgentIpAddress(); // Extract IPv6 addresses from packet if (packet.ip_protocol_version == 6) { if (root.hasSrcIpv6()) { ::capnp::Data::Reader reader_ipv6_data = root.getSrcIpv6(); if (reader_ipv6_data.size() == 16) { // Copy internal structure to C++ struct // TODO: move this code to something more high level, please memcpy((void*)&packet.src_ipv6, reader_ipv6_data.begin(), reader_ipv6_data.size()); } else { logger << log4cpp::Priority::ERROR << "broken size for IPv6 source address"; } } if (root.hasDstIpv6()) { ::capnp::Data::Reader reader_ipv6_data = root.getDstIpv6(); if (reader_ipv6_data.size() == 16) { // Copy internal structure to C++ struct // TODO: move this code to something more high level, please memcpy((void*)&packet.dst_ipv6, reader_ipv6_data.begin(), reader_ipv6_data.size()); } else { logger << log4cpp::Priority::ERROR << "broken size for IPv6 destination address"; } } // TODO: if we could not read src of dst IP addresses here we should drop this packet } packet.ttl = root.getTtl(); packet.source_port = root.getSourcePort(); packet.destination_port = root.getDestinationPort(); packet.length = root.getLength(); packet.number_of_packets = root.getNumberOfPackets(); packet.flags = root.getFlags(); packet.ip_fragmented = root.getIpFragmented(); packet.ts.tv_sec = root.getTsSec(); packet.ts.tv_usec = root.getTsMsec(); packet.captured_payload_length = root.getPacketPayloadLength(); packet.payload_full_length = root.getPacketPayloadFullLength(); packet.packet_direction = (direction_t)root.getPacketDirection(); packet.source = (source_t)root.getSource(); } catch (kj::Exception& e) { logger << log4cpp::Priority::WARN << "Exception happened during attempt to parse tera flow packet: " << e.getDescription().cStr(); return false; } catch (...) { logger << log4cpp::Priority::WARN << "Exception happened during attempt to parse tera flow packet"; return false; } return true; } // Encode simple packet into special capnp structure for serialization bool write_simple_packet(int fd, simple_packet_t& packet, bool populate_ipv6) { extern log4cpp::Category& logger; ::capnp::MallocMessageBuilder message; auto capnp_packet = message.initRoot(); capnp_packet.setProtocol(packet.protocol); capnp_packet.setSampleRatio(packet.sample_ratio); capnp_packet.setSrcIp(packet.src_ip); capnp_packet.setDstIp(packet.dst_ip); capnp_packet.setIpProtocolVersion(packet.ip_protocol_version); capnp_packet.setTtl(packet.ttl); capnp_packet.setSourcePort(packet.source_port); capnp_packet.setDestinationPort(packet.destination_port); capnp_packet.setLength(packet.length); capnp_packet.setNumberOfPackets(packet.number_of_packets); capnp_packet.setFlags(packet.flags); capnp_packet.setIpFragmented(packet.ip_fragmented); capnp_packet.setTsSec(packet.ts.tv_sec); capnp_packet.setTsMsec(packet.ts.tv_usec); capnp_packet.setPacketPayloadLength(packet.captured_payload_length); capnp_packet.setPacketPayloadFullLength(packet.payload_full_length); capnp_packet.setPacketDirection(packet.packet_direction); capnp_packet.setSource(packet.source); capnp_packet.setSrcAsn(packet.src_asn); capnp_packet.setDstAsn(packet.dst_asn); capnp_packet.setInputInterface(packet.input_interface); capnp_packet.setOutputInterface(packet.output_interface); capnp_packet.setAgentIpAddress(packet.agent_ip_address); if (populate_ipv6 && packet.ip_protocol_version == 6) { kj::ArrayPtr src_ipv6_as_kj_array((kj::byte*)&packet.src_ipv6, sizeof(packet.src_ipv6)); capnp_packet.setSrcIpv6(capnp::Data::Reader(src_ipv6_as_kj_array)); kj::ArrayPtr dst_ipv6_as_kj_array((kj::byte*)&packet.dst_ipv6, sizeof(packet.dst_ipv6)); capnp_packet.setDstIpv6(capnp::Data::Reader(dst_ipv6_as_kj_array)); } // Capnp uses exceptions, let's wrap them out try { // For some unknown for me reasons this function sends incorrect (very short) data // writePackedMessageToFd(fd, message); // Instead I'm using less optimal (non zero copy) approach but it's working well kj::Array words = messageToFlatArray(message); kj::ArrayPtr bytes = words.asBytes(); ssize_t write_result = write(fd, bytes.begin(), bytes.size()); // If write returned error then stop processing if (write_result < 0) { // If we received error from it, let's provide details about it in DEBUG mode if (write_result == -1) { logger << log4cpp::Priority::DEBUG << "write in write_simple_packet returned error: " << errno; } return false; } // we could not write whole packet notify caller about it if (write_result != bytes.size()) { logger << log4cpp::Priority::DEBUG << "write in write_simple_packet did not write all data"; return false; } } catch (...) { // logger << log4cpp::Priority::ERROR << "writeSimplePacket failed with error"; return false; } return true; } #endif // Represent IPv6 cidr subnet in string form std::string print_ipv6_cidr_subnet(subnet_ipv6_cidr_mask_t subnet) { return print_ipv6_address(subnet.subnet_address) + "/" + std::to_string(subnet.cidr_prefix_length); } // Abstract function with overloads for templated classes where we use v4 and v4 std::string convert_any_ip_to_string(const subnet_ipv6_cidr_mask_t& subnet) { return convert_ipv6_subnet_to_string(subnet); } // Return true if we have this IP in patricia tree bool ip_belongs_to_patricia_tree_ipv6(patricia_tree_t* patricia_tree, struct in6_addr client_ipv6_address) { prefix_t prefix_for_check_address; prefix_for_check_address.family = AF_INET6; prefix_for_check_address.bitlen = 128; prefix_for_check_address.add.sin6 = client_ipv6_address; return patricia_search_best2(patricia_tree, &prefix_for_check_address, 1) != NULL; } // Safe way to convert string to positive integer. // We accept only positive numbers here bool convert_string_to_positive_integer_safe(std::string line, int& value) { int temp_value = 0; try { temp_value = std::stoi(line); } catch (...) { // Could not parse number correctly return false; } if (temp_value >= 0) { value = temp_value; return true; } else { // We do not expect negative values here return false; } return true; } // Read IPv6 host address from string representation bool read_ipv6_host_from_string(std::string ipv6_host_as_string, in6_addr& result) { if (inet_pton(AF_INET6, ipv6_host_as_string.c_str(), &result) == 1) { return true; } else { return false; } } // Validates IPv4 or IPv6 address in host form: // 127.0.0.1 or ::1 bool validate_ipv6_or_ipv4_host(const std::string host) { // Validate host address boost::system::error_code ec; // Try to build it from string representation boost::asio::ip::address::from_string(host, ec); // If we failed to parse it if (ec) { return false; } return true; } // We expect something like: 122.33.11.22:8080/somepath here // And return: 122.33.11.22, 8080 and "/somepath" as separate parts bool split_full_url(std::string full_url, std::string& host, std::string& port, std::string& path) { auto delimiter_position = full_url.find("/"); if (delimiter_position == std::string::npos) { host = full_url; path = ""; } else { host = full_url.substr(0, delimiter_position); // Add all symbols until the end of line to the path path = full_url.substr(delimiter_position, std::string::npos); } auto port_delimiter_position = host.find(":"); // Let's try to extract port if we have ":" delimiter in host if (port_delimiter_position != std::string::npos) { std::vector splitted_host; split(splitted_host, host, boost::is_any_of(":"), boost::token_compress_on); if (splitted_host.size() != 2) { return false; } host = splitted_host[0]; port = splitted_host[1]; } return true; } // Encrypted version of execute_web_request bool execute_web_request_secure(std::string address, std::string request_type, std::string post_data, uint32_t& response_code, std::string& response_body, std::map& headers, std::string& error_text) { extern log4cpp::Category& logger; std::string host; std::string path; std::string port = "443"; if (address.find("https://") == std::string::npos) { logger << log4cpp::Priority::ERROR << "URL has not supported protocol prefix: " << address; logger << log4cpp::Priority::ERROR << "We have support only for https"; return false; } // Remove URL prefix boost::replace_all(address, "https://", ""); bool split_result = split_full_url(address, host, port, path); if (!split_result) { logger << log4cpp::Priority::ERROR << "Could not split URL into components"; return false; } if (request_type != "post" && request_type != "get") { logger << log4cpp::Priority::ERROR << "execute_web_request has support only for post and get requests"; return false; } // If customer uses address like: 11.22.33.44:8080 without any path we should add it manually to comply with http protocol if (path == "") { path = "/"; } try { boost::system::error_code ec; boost::asio::io_context ioc; // The SSL context is required, and holds certificates boost::asio::ssl::context ctx{ boost::asio::ssl::context::tls_client }; // Load default CA certificates ctx.set_default_verify_paths(); boost::asio::ip::tcp::resolver resolver{ ioc }; boost::asio::ssl::stream stream{ ioc, ctx }; // Set SNI Hostname if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) { boost::system::error_code ec{ static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; logger << log4cpp::Priority::ERROR << "Can't set SNI hostname: " << ec.message(); return false; } auto end_point = resolver.resolve(boost::asio::ip::tcp::resolver::query{ host, port }, ec); if (ec) { logger << log4cpp::Priority::ERROR << "Could not resolve peer address in execute_web_request " << ec; return false; } logger << log4cpp::Priority::DEBUG << "Resolved host " << host << " to " << end_point.size() << " IP addresses"; boost::asio::connect(stream.next_layer(), end_point.begin(), end_point.end(), ec); if (ec) { logger << log4cpp::Priority::ERROR << "Could not connect to peer in execute_web_request " << ec.message(); return false; } stream.handshake(boost::asio::ssl::stream_base::client, ec); if (ec) { logger << log4cpp::Priority::ERROR << "SSL handshake failed " << ec.message(); return false; } // logger << log4cpp::Priority::INFO << "SSL connection established"; // Send HTTP request using beast boost::beast::http::request req; if (request_type == "post") { req.method(boost::beast::http::verb::post); } else if (request_type == "get") { req.method(boost::beast::http::verb::get); } for (const auto& [k, v] : headers) { req.set(k, v); } req.target(path); req.version(11); // Pass data only for post request if (request_type == "post") { req.body() = post_data; } std::string content_type = "application/x-www-form-urlencoded"; // We can override Content Type from headers auto header_itr = headers.find("Content-Type"); if (header_itr != headers.end()) { content_type = header_itr->second; } req.set(boost::beast::http::field::content_type, content_type); // We must specify port explicitly if we use non standard one std::string full_host = host + ":" + std::to_string(stream.next_layer().remote_endpoint().port()); // logger << log4cpp::Priority::INFO << "I will use " << full_host << " as host"; req.set(boost::beast::http::field::host, full_host.c_str()); // TBD: we also should add port number to host name if we use non standard one // + ":" + std::to_string(end_point.port())); req.set(boost::beast::http::field::user_agent, "FastNetMon"); req.prepare_payload(); boost::beast::http::write(stream, req, ec); if (ec) { logger << log4cpp::Priority::ERROR << "Could not write data to socket in execute_web_request: " << ec.message(); return false; } // Receive and print HTTP response using beast // This buffer is used for reading and must be persisted boost::beast::flat_buffer b; boost::beast::http::response resp; boost::beast::http::read(stream, b, resp, ec); if (ec) { logger << log4cpp::Priority::ERROR << "Could not read data inside execute_web_request: " << ec.message(); return false; } response_code = resp.result_int(); // Return response body to caller response_body = resp.body(); logger << log4cpp::Priority::DEBUG << "Response code: " << response_code; logger << log4cpp::Priority::DEBUG << "Prepare to shutdown TLS"; stream.shutdown(ec); if (ec == boost::asio::error::eof) { // Rationale: // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error ec.assign(0, ec.category()); } logger << log4cpp::Priority::DEBUG << "Successfully closed TLS"; return true; } catch (std::exception& e) { logger << log4cpp::Priority::ERROR << "execute_web_request failed with error: " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "execute_web_request failed with unknown error"; return false; } return false; } bool execute_web_request(std::string address, std::string request_type, std::string post_data, uint32_t& response_code, std::string& response_body, std::map& headers, std::string& error_text) { std::string host; std::string path; std::string port = "http"; if (address.find("https://") != std::string::npos) { return execute_web_request_secure(address, request_type, post_data, response_code, response_body, headers, error_text); } if (address.find("http://") == std::string::npos) { error_text = "URL has not supported protocol prefix: " + address; return false; } // Remove URL prefix boost::replace_all(address, "http://", ""); bool split_result = split_full_url(address, host, port, path); if (!split_result) { error_text = "Could not split URL into components"; return false; } // If customer uses address like: 11.22.33.44:8080 without any path we should add it manually to comply with http protocol if (path == "") { path = "/"; } if (request_type != "post" && request_type != "get") { error_text = "execute_web_request has support only for post and get requests. Requested: "; error_text += request_type; return false; } try { boost::system::error_code ec; // Normal boost::asio setup // std::string const host = "178.62.227.110"; boost::asio::io_service ios; boost::asio::ip::tcp::resolver r(ios); boost::asio::ip::tcp::socket sock(ios); auto end_point = r.resolve(boost::asio::ip::tcp::resolver::query{ host, port }, ec); if (ec) { error_text = "Could not resolve peer address in execute_web_request " + ec.message(); return false; } boost::asio::connect(sock, end_point, ec); if (ec) { error_text = "Could not connect to peer in execute_web_request " + ec.message(); return false; } // Send HTTP request using beast boost::beast::http::request req; if (request_type == "post") { req.method(boost::beast::http::verb::post); } else if (request_type == "get") { req.method(boost::beast::http::verb::get); } for (const auto& [k, v] : headers) { req.set(k, v); } req.target(path); req.version(11); // Pass data only for post request if (request_type == "post") { req.body() = post_data; } std::string content_type = "application/x-www-form-urlencoded"; // We can override Content Type from headers auto header_itr = headers.find("Content-Type"); if (header_itr != headers.end()) { content_type = header_itr->second; } req.set(boost::beast::http::field::content_type, content_type); req.set(boost::beast::http::field::host, host + ":" + std::to_string(sock.remote_endpoint().port())); req.set(boost::beast::http::field::user_agent, "FastNetMon"); req.prepare_payload(); boost::beast::http::write(sock, req, ec); if (ec) { error_text = "Could not write data to socket in execute_web_request: " + ec.message(); return false; } // Receive and print HTTP response using beast // This buffer is used for reading and must be persisted boost::beast::flat_buffer b; boost::beast::http::response resp; boost::beast::http::read(sock, b, resp, ec); if (ec) { error_text = "Could not read data inside execute_web_request: "; error_text += ec.message(); return false; } response_code = resp.result_int(); response_body = resp.body(); using tcp = boost::asio::ip::tcp; // Gracefully close the socket sock.shutdown(tcp::socket::shutdown_both, ec); // We ignore ec error here from shutdown return true; } catch (std::exception& e) { error_text = "execute_web_request failed with error: "; error_text += e.what(); return false; } catch (...) { error_text = "execute_web_request failed with unknown error"; return false; } return false; } // Write data to influxdb bool write_data_to_influxdb(const std::string& database, const std::string& host, const std::string& port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& query, std::string& error_text) { uint32_t response_code = 0; std::string address = host + ":" + port; std::string influxdb_query_string = std::string("http://") + address + "/write?db=" + database; // Add auth credentials if (enable_auth) { influxdb_query_string += "&u=" + influx_user + "&p=" + influx_password; } // TODO: I have an idea to reduce number of active TIME_WAIT connections and we have function // execute_web_request_connection_close // But I suppose issues on InfluxDB side and raised ticket about it // https://github.com/influxdata/influxdb/issues/8525 // And we could not switch to it yet // We do not need it here but function requires this option std::string response_body; std::map headers; bool result = execute_web_request(influxdb_query_string, "post", query, response_code, response_body, headers, error_text); if (!result) { return false; } if (response_code != 204) { error_text = "Unexpected response code: " + std::to_string(response_code); return false; } return true; } uint64_t get_current_unix_time_in_nanoseconds() { auto unix_timestamp = std::chrono::seconds(std::time(NULL)); uint64_t unix_timestamp_nanoseconds = std::chrono::milliseconds(unix_timestamp).count() * 1000 * 1000; return unix_timestamp_nanoseconds; } // Joins data to format a=b,d=f std::string join_by_comma_and_equal(const std::map& data) { std::stringstream buffer; for (auto itr = data.begin(); itr != data.end(); ++itr) { buffer << itr->first << "=" << itr->second; // it's last element if (std::distance(itr, data.end()) == 1) { // Do not print comma } else { buffer << ","; } } return buffer.str(); } // We will store option name as key and value will be memory size in bytes bool parse_meminfo_into_map(std::map& parsed_meminfo) { extern log4cpp::Category& logger; std::ifstream meminfo_file("/proc/meminfo"); boost::regex memory_info_pattern("^(.*?):\\s+(\\d+).*$", boost::regex::icase); if (!meminfo_file.is_open()) { logger << log4cpp::Priority::ERROR << "Could not open meminfo file"; return false; } std::string line; while (getline(meminfo_file, line)) { // MemTotal: 501912 kB boost::match_results regex_results; if (boost::regex_match(line, regex_results, memory_info_pattern)) { uint64_t memory_value = 0; bool integer_parser_result = read_uint64_from_string(regex_results[2], memory_value); if (!integer_parser_result) { logger << log4cpp::Priority::ERROR << "Could not parse " << regex_results[2] << " as unsigned 64 bit integer"; return false; } parsed_meminfo[regex_results[1]] = memory_value * 1024; } } return true; } // Reads uint64_t from string with all required safety checks bool read_uint64_from_string(const std::string& line, uint64_t& value) { uint64_t temp_value = 0; try { // Read value to intermediate variable to avoid interference with argument of function in case of failure // NB! This function does not work very well when we have minus in input sequence as it will accept it // If the minus sign was part of the input sequence, the numeric value calculated from the sequence of digits // is negated as if by unary minus in the result type, which applies unsigned integer wraparound rules. temp_value = std::stoull(line); } catch (...) { return false; } value = temp_value; return true; } bool read_file_to_string(const std::string& file_path, std::string& file_content) { std::ifstream file_handler; file_handler.open(file_path, std::ios::in); if (file_handler.is_open()) { std::stringstream str_stream; str_stream << file_handler.rdbuf(); file_handler.close(); file_content = str_stream.str(); return true; } else { return false; } } bool read_integer_from_file(const std::string& file_path, int& value) { std::string file_content_in_string; bool read_file_to_string_result = read_file_to_string(file_path, file_content_in_string); if (!read_file_to_string_result) { return false; } int scanned_value = 0; bool read_integer_from_file = convert_string_to_any_integer_safe(file_content_in_string, scanned_value); if (!read_integer_from_file) { return false; } value = scanned_value; return true; } // Safe way to convert string to any integer bool convert_string_to_any_integer_safe(const std::string& line, int& value) { int temp_value = 0; try { temp_value = std::stoi(line); } catch (...) { // Could not parse number correctly return false; } value = temp_value; return true; } // This function is useful when we start it from thread and detach and so we are not interested in error text and we need to discard it void exec_no_error_check(const std::string& cmd) { std::string error_text; std::vector output_list; exec(cmd, output_list, error_text); return; } unsigned int get_logical_cpus_number() { extern log4cpp::Category& logger; std::ifstream cpuinfo_file("/proc/cpuinfo"); boost::regex processor_pattern("^processor.*?$"); if (!cpuinfo_file.is_open()) { logger << log4cpp::Priority::ERROR << "License: could not open cpuinfo"; return 0; } std::string line; unsigned int logical_cpus_number = 0; while (getline(cpuinfo_file, line)) { boost::cmatch what; if (regex_match(line.c_str(), what, processor_pattern)) { logical_cpus_number++; } } return logical_cpus_number; } // Get server's total memory in megabytes unsigned int get_total_memory() { extern log4cpp::Category& logger; std::ifstream meminfo_file("/proc/meminfo"); boost::regex memory_info_pattern("^(.*?):\\s+(\\d+).*$", boost::regex::icase); if (!meminfo_file.is_open()) { logger << log4cpp::Priority::ERROR << "License: could not open meminfo file"; return 0; } std::string line; while (getline(meminfo_file, line)) { // MemTotal: 501912 kB boost::match_results regex_results; if (boost::regex_match(line, regex_results, memory_info_pattern)) { if (regex_results[1] == "MemTotal") { int memory_amount = 0; bool conversion_result = convert_string_to_any_integer_safe(regex_results[2], memory_amount); if (!conversion_result) { logger << log4cpp::Priority::ERROR << "Could not parse integer value"; return 0; } return unsigned(memory_amount / 1024); } } else { logger << log4cpp::Priority::ERROR << "Could not parse line in /proc/meminfo: " << line; return 0; } } return 0; } // Return code name of Linux distro: // ID=debian // ID="centos" // ID=ubuntu bool get_linux_distro_name(std::string& distro_name) { std::map parsed_file; if (!parse_os_release_into_map(parsed_file)) { return false; } auto itr = parsed_file.find("ID"); if (itr == parsed_file.end()) { return false; } distro_name = itr->second; return true; } // Returns Linux distro version // VERSION_ID="11" // VERSION_ID="8" // VERSION_ID="7" // VERSION_ID="16.04" bool get_linux_distro_version(std::string& distro_version) { std::map parsed_file; if (!parse_os_release_into_map(parsed_file)) { return false; } auto itr = parsed_file.find("VERSION_ID"); if (itr == parsed_file.end()) { return false; } distro_version = itr->second; return true; } // We will store option name as key and value will be value bool parse_os_release_into_map(std::map& parsed_os_release) { extern log4cpp::Category& logger; // Format: https://www.freedesktop.org/software/systemd/man/os-release.html std::ifstream os_release_file("/etc/os-release"); // Split line like: // ID="centos" boost::regex os_release_pattern("^(.*?)=\"?(.*?)\"?$", boost::regex::icase); if (!os_release_file.is_open()) { logger << log4cpp::Priority::ERROR << "Could not open /etc/os-release file"; return false; } std::string line; while (getline(os_release_file, line)) { // ID="centos" // VERSION_ID="7" boost::match_results regex_results; if (boost::regex_match(line, regex_results, os_release_pattern)) { std::string value = regex_results[2]; // We may have or may not have quotes for value, strip them boost::replace_all(value, "\"", ""); parsed_os_release[regex_results[1]] = value; } } return true; } // Returns virtualisation method or "unknown" // It may have dash in value like "vm-other" or "lxc-libvirt" but no other symbols are expected std::string get_virtualisation_method() { std::string error_text; std::vector output; bool exec_result = exec("systemd-detect-virt --vm", output, error_text); if (!exec_result) { return "unknown"; } if (output.empty()) { return "unknown"; } // Return first element return boost::algorithm::to_lower_copy(output[0]); } #ifdef _WIN32 bool get_kernel_version(std::string& kernel_version) { kernel_version = "windows"; return true; } #else // Get linux kernel version in form: 3.19.0-25-generic bool get_kernel_version(std::string& kernel_version) { struct utsname current_utsname; int uname_result = uname(¤t_utsname); if (uname_result != 0) { return false; } // Release field is a char array (char release[], http://man7.org/linux/man-pages/man2/uname.2.html) and we do not need NULL check here kernel_version = std::string(current_utsname.release); return true; } #endif // Returns all CPU flags in vector bool get_cpu_flags(std::vector& flags) { extern log4cpp::Category& logger; std::ifstream cpuinfo_file("/proc/cpuinfo"); boost::regex processor_flags_pattern("^flags\\s+:\\s(.*?)$"); if (!cpuinfo_file.is_open()) { logger << log4cpp::Priority::ERROR << "Could not open cpuinfo"; return false; } std::string line; while (getline(cpuinfo_file, line)) { boost::match_results regex_results; if (boost::regex_match(line, regex_results, processor_flags_pattern)) { // Split all flags by space split(flags, regex_results[1], boost::is_any_of(" "), boost::token_compress_on); return true; } } logger << log4cpp::Priority::ERROR << "Cannot find any flags in cpuinfo"; return false; } bool get_cpu_model(std::string& cpu_model) { extern log4cpp::Category& logger; std::ifstream cpuinfo_file("/proc/cpuinfo"); if (!cpuinfo_file.is_open()) { logger << log4cpp::Priority::ERROR << "Could not open /proc/cpuinfo"; return false; } boost::regex processor_model_pattern("^model name\\s+:\\s(.*?)$"); std::string line; while (getline(cpuinfo_file, line)) { boost::match_results regex_results; if (boost::regex_match(line, regex_results, processor_model_pattern)) { cpu_model = regex_results[1]; return true; } } // For ARM CPUs we have another format // Even if we run in x86_64 mode we can have cpuinfo with such information on ARM64 based macOS platforms std::string implementer; std::string part; std::string revision; boost::regex implementer_pattern("^CPU implementer\\s+:\\s(.*?)$"); boost::regex part_pattern("^CPU part\\s+:\\s(.*?)$"); boost::regex revision_pattern("^CPU revision\\s+:\\s(.*?)$"); // Reset to start of file cpuinfo_file.clear(); cpuinfo_file.seekg(0, std::ios::beg); while (getline(cpuinfo_file, line)) { boost::match_results regex_results_implementer; boost::match_results regex_results_part; boost::match_results regex_results_revision; if (boost::regex_match(line, regex_results_implementer, implementer_pattern)) { implementer = regex_results_implementer[1]; } if (boost::regex_match(line, regex_results_part, part_pattern)) { part = regex_results_part[1]; } if (boost::regex_match(line, regex_results_revision, revision_pattern)) { revision = regex_results_revision[1]; } } // If we fould all of them, use these fields as model if (implementer.size() > 0 && part.size() > 0 && revision.size() > 0) { cpu_model = "implementer: " + implementer + " part: " + part + " revision: " + revision; return true; } // logger << log4cpp::Priority::ERROR << "implementer: " << implementer << " part: " << part << " revision: " << revision; return false; } // returns forwarding status as string std::string forwarding_status_to_string(forwarding_status_t status) { if (status == forwarding_status_t::unknown) { return "unknown"; } else if (status == forwarding_status_t::forwarded) { return "forwarded"; } else if (status == forwarding_status_t::dropped) { return "dropped"; } else if (status == forwarding_status_t::consumed) { return "consumed"; } else { // It must not happen return "unknown"; } } // Pretty strange function to implement country code conversion we use in fastnetmon_simple_packet std::string country_static_string_to_dynamic_string(const boost::beast::static_string<2>& country_code) { std::string country_code_dynamic_string; if (country_code.size() == 2) { country_code_dynamic_string += country_code[0]; country_code_dynamic_string += country_code[1]; } return country_code_dynamic_string; } #ifdef _WIN32 // We have no inet_aton on Windows but we do have inet_pton https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-inet_pton // Convert IP in string representation to uint32_t in big endian (network byte order) // I think we can switch to using pton for Linux and other *nix too but we need to do careful testing including performance evaluation before bool convert_ip_as_string_to_uint_safe(const std::string& ip, uint32_t& ip_as_integer) { struct in_addr ip_addr; // Both Windows and Linux return 1 in case of success if (inet_pton(AF_INET, ip.c_str(), &ip_addr) != 1) { return false; } // in network byte order ip_as_integer = ip_addr.s_addr; return true; } #else // Convert IP in string representation to uint32_t in big endian (network byte order) bool convert_ip_as_string_to_uint_safe(const std::string& ip, uint32_t& ip_as_integer) { struct in_addr ip_addr; // Please be careful! This function uses pretty strange approach for returned codes // inet_aton() returns nonzero if the address is valid, zero if not. if (inet_aton(ip.c_str(), &ip_addr) == 0) { return false; } // in network byte order ip_as_integer = ip_addr.s_addr; return true; } #endif forwarding_status_t forwarding_status_from_integer(uint8_t forwarding_status_as_integer) { // Decode numbers into forwarding statuses // I think they're same for Netflow v9 https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html // and IPFIX: https://datatracker.ietf.org/doc/html/rfc7270#section-4.12 if (forwarding_status_as_integer == 0) { return forwarding_status_t::unknown; } else if (forwarding_status_as_integer == 1) { return forwarding_status_t::forwarded; } else if (forwarding_status_as_integer == 2) { return forwarding_status_t::dropped; } else if (forwarding_status_as_integer == 3) { return forwarding_status_t::consumed; } else { // It must not happen return forwarding_status_t::unknown; } } // Represent IPv6 subnet in string form std::string convert_ipv6_subnet_to_string(const subnet_ipv6_cidr_mask_t& subnet) { return print_ipv6_address(subnet.subnet_address) + "/" + std::to_string(subnet.cidr_prefix_length); } std::string convert_any_ip_to_string(uint32_t client_ip) { return convert_ip_as_uint_to_string(client_ip); } // This code lookup IP in specified patricia tree and returns prefix which it // belongs bool lookup_ip_in_integer_form_inpatricia_and_return_subnet_if_found(patricia_tree_t* patricia_tree, uint32_t client_ip, subnet_cidr_mask_t& subnet) { if (patricia_tree == NULL) { return false; } prefix_t prefix_for_check_address; prefix_for_check_address.add.sin.s_addr = client_ip; prefix_for_check_address.family = AF_INET; prefix_for_check_address.bitlen = 32; patricia_node_t* found_patrica_node = patricia_search_best2(patricia_tree, &prefix_for_check_address, 1); if (found_patrica_node == NULL) { return false; } prefix_t* prefix = found_patrica_node->prefix; if (prefix == NULL) { return false; } subnet.subnet_address = prefix->add.sin.s_addr; subnet.cidr_prefix_length = prefix->bitlen; return true; } // Return true if we have this IP in patricia tree bool ip_belongs_to_patricia_tree(patricia_tree_t* patricia_tree, uint32_t client_ip) { prefix_t prefix_for_check_address; prefix_for_check_address.add.sin.s_addr = client_ip; prefix_for_check_address.family = AF_INET; prefix_for_check_address.bitlen = 32; return patricia_search_best2(patricia_tree, &prefix_for_check_address, 1) != NULL; } // Overloaded function which works with any IP protocol version, we use it for templated applications std::string convert_any_subnet_to_string(const subnet_ipv6_cidr_mask_t& subnet) { return convert_ipv6_subnet_to_string(subnet); } std::string convert_any_subnet_to_string(const subnet_cidr_mask_t& subnet) { return convert_ipv4_subnet_to_string(subnet); } std::string print_binary_string_as_hex_with_leading_0x(const uint8_t* data_ptr, uint32_t data_length) { std::stringstream buffer; for (uint32_t i = 0; i < data_length; i++) { buffer << "0x" << std::setfill('0') << std::setw(2) << std::hex << uint32_t(data_ptr[i]) << " "; } return buffer.str(); } bool read_ipv6_subnet_from_string(subnet_ipv6_cidr_mask_t& ipv6_address, const std::string& ipv6_subnet_as_string) { extern log4cpp::Category& logger; std::vector subnet_as_string; split(subnet_as_string, ipv6_subnet_as_string, boost::is_any_of("/"), boost::token_compress_on); if (subnet_as_string.size() != 2) { return false; } int cidr = 0; bool conversion_result = convert_string_to_any_integer_safe(subnet_as_string[1], cidr); if (!conversion_result) { return false; } ipv6_address.cidr_prefix_length = cidr; bool parsed_ipv6 = read_ipv6_host_from_string(subnet_as_string[0], ipv6_address.subnet_address); if (!parsed_ipv6) { logger << log4cpp::Priority::ERROR << "Can't parse IPv6 address: " << ipv6_subnet_as_string; return false; } return true; } // Return true if we have this subnet in patricia tree bool subnet_belongs_to_patricia_tree(patricia_tree_t* patricia_tree, const subnet_cidr_mask_t& subnet) { prefix_t prefix_for_check_adreess; prefix_for_check_adreess.add.sin.s_addr = subnet.subnet_address; prefix_for_check_adreess.family = AF_INET; prefix_for_check_adreess.bitlen = subnet.cidr_prefix_length; patricia_node_t* found_patrica_node = patricia_search_best2(patricia_tree, &prefix_for_check_adreess, 1); if (found_patrica_node != NULL) { return true; } else { return false; } } // Prepares textual dump of simple packets buffer void print_simple_packet_buffer_to_string(const boost::circular_buffer& simple_packets_buffer, std::string& output) { if (simple_packets_buffer.size() != 0) { std::stringstream ss; for (const simple_packet_t& packet : simple_packets_buffer) { ss << print_simple_packet(packet); } output = ss.str(); } } // Write circular buffer with simple packets to json document bool write_simple_packet_as_separate_fields_dump_to_json(const boost::circular_buffer& simple_packets_buffer, nlohmann::json& packet_array) { extern log4cpp::Category& logger; // Even if we have no data we need empty array here packet_array = nlohmann::json::array(); try { if (simple_packets_buffer.size() == 0) { logger << log4cpp::Priority::INFO << "Packet buffer is blank"; return true; } // Add all pack descriptions as strings array for (const simple_packet_t& packet : simple_packets_buffer) { nlohmann::json json_packet; if (!serialize_simple_packet_to_json(packet, json_packet)) { continue; } // Append to document as normal STL container packet_array.push_back(json_packet); } } catch (...) { logger << log4cpp::Priority::ERROR << "Cannot create packet list in JSON"; return false; } return true; } fastnetmon-1.2.8/src/fast_library.hpp000066400000000000000000000216771472727706000177120ustar00rootroot00000000000000#pragma once #include "fastnetmon_types.hpp" #include #include #include #include #include #include #include #include "nlohmann/json.hpp" #include "libpatricia/patricia.hpp" #include "fastnetmon_networks.hpp" #include "attack_details.hpp" #include #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(const std::string& interface_name); 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); bool exec(const std::string& cmd, std::vector& output_list, std::string& error_text); 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(const struct in6_addr& ipv6_address); std::string print_simple_packet(simple_packet_t packet); std::string convert_timeval_to_date(const timeval& tv); bool convert_hex_as_string_to_uint(std::string hex, uint32_t& value); 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); 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(std::string 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); int convert_string_to_integer(std::string line); 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_t get_packet_direction(patricia_tree_t* lookup_tree, uint32_t src_ip, uint32_t dst_ip, subnet_cidr_mask_t& subnet); direction_t get_packet_direction_ipv6(patricia_tree_t* lookup_tree, struct in6_addr src_ipv6, struct in6_addr dst_ipv6, subnet_ipv6_cidr_mask_t& subnet); std::string convert_prefix_to_string_representation(prefix_t* prefix); std::string convert_subnet_to_string(subnet_cidr_mask_t my_subnet); std::string get_direction_name(direction_t direction_value); std::vector split_strings_to_vector_by_comma(std::string raw_string); bool convert_subnet_from_string_to_binary_with_cidr_format_safe(const std::string& subnet_cidr, subnet_cidr_mask_t& subnet_cidr_mask); #ifdef __linux__ bool manage_interface_promisc_mode(std::string interface_name, bool switch_on); bool get_interface_number_by_device_name(int socket_fd, std::string interface_name, int& interface_number); #endif bool ip_belongs_to_patricia_tree_ipv6(patricia_tree_t* patricia_tree, struct in6_addr client_ipv6_address); std::string serialize_attack_description(const attack_details_t& current_attack); attack_type_t detect_attack_type(const attack_details_t& current_attack); std::string get_printable_attack_name(attack_type_t attack); std::string serialize_network_load_to_text(subnet_counter_t& network_speed_meter, bool average); 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); bool set_boost_process_name(boost::thread* thread, const std::string& process_name); std::string convert_subnet_to_string(subnet_cidr_mask_t my_subnet); std::string print_ipv6_cidr_subnet(subnet_ipv6_cidr_mask_t subnet); std::string convert_any_ip_to_string(const subnet_ipv6_cidr_mask_t& subnet); bool convert_string_to_positive_integer_safe(std::string line, int& value); bool read_ipv6_host_from_string(std::string ipv6_host_as_string, in6_addr& result); bool validate_ipv6_or_ipv4_host(const std::string host); uint64_t get_current_unix_time_in_nanoseconds(); bool write_data_to_influxdb(const std::string& database, const std::string& host, const std::string& port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& query, std::string& error_text); std::string join_by_comma_and_equal(const std::map& data); bool parse_meminfo_into_map(std::map& parsed_meminfo); bool read_uint64_from_string(const std::string& line, uint64_t& value); bool read_integer_from_file(const std::string& file_path, int& value); bool read_file_to_string(const std::string& file_path, std::string& file_content); bool convert_string_to_any_integer_safe(const std::string& line, int& value); void exec_no_error_check(const std::string& cmd); bool parse_os_release_into_map(std::map& parsed_os_release); unsigned int get_logical_cpus_number(); std::string get_virtualisation_method(); bool get_cpu_flags(std::vector& flags); bool get_linux_distro_name(std::string& distro_name); bool get_linux_distro_version(std::string& distro_name); bool get_kernel_version(std::string& kernel_version); bool execute_web_request(const std::string& address_param, const std::string& request_type, const std::string& post_data, uint32_t& response_code, std::string& response_body, const std::map& headers, std::string& error_text); unsigned int get_total_memory(); bool get_cpu_model(std::string& cpu_model); bool execute_web_request_secure(std::string address, std::string request_type, std::string post_data, uint32_t& response_code, std::string& response_body, std::map& headers, std::string& error_text); std::string forwarding_status_to_string(forwarding_status_t status); std::string country_static_string_to_dynamic_string(const boost::beast::static_string<2>& country_code); bool serialize_simple_packet_to_json(const simple_packet_t& packet, nlohmann::json& json_packet); bool convert_ip_as_string_to_uint_safe(const std::string& ip, uint32_t& ip_as_integer); forwarding_status_t forwarding_status_from_integer(uint8_t forwarding_status_as_integer); bool is_zero_ipv6_address(const in6_addr& ipv6_address); std::string convert_ipv4_subnet_to_string(const subnet_cidr_mask_t& subnet); // Represent IPv6 subnet in string form std::string convert_ipv6_subnet_to_string(const subnet_ipv6_cidr_mask_t& subnet); std::string convert_any_ip_to_string(uint32_t client_ip); bool lookup_ip_in_integer_form_inpatricia_and_return_subnet_if_found(patricia_tree_t* patricia_tree, uint32_t client_ip, subnet_cidr_mask_t& subnet); bool ip_belongs_to_patricia_tree(patricia_tree_t* patricia_tree, uint32_t client_ip); // Overloaded function which works with any IP protocol version, we use it for templated applications std::string convert_any_subnet_to_string(const subnet_ipv6_cidr_mask_t& subnet); std::string convert_any_subnet_to_string(const subnet_cidr_mask_t& subnet); std::string print_binary_string_as_hex_with_leading_0x(const uint8_t* data_ptr, uint32_t data_length); bool read_ipv6_subnet_from_string(subnet_ipv6_cidr_mask_t& ipv6_address, const std::string& ipv6_subnet_as_string); bool subnet_belongs_to_patricia_tree(patricia_tree_t* patricia_tree, const subnet_cidr_mask_t& subnet); // Prepares textual dump of simple packets buffer void print_simple_packet_buffer_to_string(const boost::circular_buffer& simple_packets_buffer, std::string& output); bool write_simple_packet_as_separate_fields_dump_to_json(const boost::circular_buffer& simple_packets_buffer, nlohmann::json& packet_array); fastnetmon-1.2.8/src/fast_platform.h.template000066400000000000000000000021651472727706000213330ustar00rootroot00000000000000#pragma once // This file is automatically generated for your platform with cmake, please do not edit it manually class FastnetmonPlatformConfigurtion { public: std::string fastnetmon_version = "${FASTNETMON_APPLICATION_VERSION}"; std::string pid_path = "${FASTNETMON_PID_PATH}"; std::string global_config_path = "${FASTNETMON_CONFIGURATION_PATH}"; std::string log_file_path = "${FASTNETMON_LOG_FILE_PATH}"; std::string attack_details_folder = "${FASTNETMON_ATTACK_DETAILS_FOLDER}"; // Default path to notify script std::string notify_script_path = "${FASTNETMON_NOTIFY_SCRIPT_PATH_DEFAULT}"; // Default path to file with networks for whitelising std::string white_list_path = "${FASTNETMON_NETWORK_WHITELIST_PATH}"; // Default path to file with all networks listing std::string networks_list_path = "${FASTNETMON_NETWORKS_LIST_PATH}"; // Path to temporarily store backtrace when fatal failure happened std::string backtrace_path = "${FASTNETMON_BACKTRACE_PATH}"; // Path to whitelist rules std::string whitelist_rules_path = "${FASTNETMON_WHITELIST_RULES_PATH}"; }; fastnetmon-1.2.8/src/fast_priority_queue.cpp000066400000000000000000000044561472727706000213220ustar00rootroot00000000000000bool 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.2.8/src/fast_priority_queue.hpp000066400000000000000000000014421472727706000213170ustar00rootroot00000000000000#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.2.8/src/fastnetmon.conf000066400000000000000000000263731472727706000175430ustar00rootroot00000000000000### ### Main configuration params ### ### Logging configuration # Logging level, can be info or debug logging_level = info # 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 # To make FastNetMon better we need to know how you use it and what's your software and hardware platform. # To accomplish this FastNetMon sends usage information every 1 hour to our statistics server https://community-stats.fastnetmon.com # We keep high standards of data protection and you can find our privacy policy here: https://community-stats.fastnetmon.com # You can find information which is being sent at GitHub: https://github.com/pavel-odintsov/fastnetmon/search?q=send_usage_data_to_reporting_server # If you prefer to disable this capability you need to set following flag to on disable_usage_report = off # Enable/Disable any actions in case of attack enable_ban = on # Enable ban for IPv6 enable_ban_ipv6 = on # disable processing for certain direction of traffic process_incoming_traffic = on process_outgoing_traffic = on # dump all traffic to log file dump_all_traffic = off # dump other traffic to log, useful to detect missed prefixes dump_other_traffic = off # How many packets will be collected from attack traffic ban_details_records_count = 20 # 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 # 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 = on # 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 do not implement per protocol flow limits due to flow calculation logic limitations # 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 ### # # Default option for port mirror capture on Linux # AF_PACKET capture engine mirror_afpacket = off # High efficient XDP based traffic capture method # XDP will detach network interface from Linux network stack completely and you may lose connectivity if your route management traffic over same interface # You need to have separate network card for management interface mirror_afxdp = off # Activates poll based logic to check for new packets. Generally, it eliminates active polling and reduces CPU load poll_mode_xdp = off # Set interface into promisc mode automatically xdp_set_promisc = on # Explicitly enable zero copy mode, requires driver support zero_copy_xdp = off # Forces native XDP mode which requires support from network card force_native_mode_xdp = off # Switch to using IP length as packet length instead of data from capture engine. Must be enabled when traffic is cropped externally xdp_read_packet_length_from_ip_header = off # Path to XDP microcode programm for packet processing microcode_xdp_path = /etc/xdp_kernel.o # You can use this option to multiply all incoming traffc by this value # It may be useful for sampled mirror ports mirror_af_packet_custom_sampling_rate = 1 # AF_PACKET fanout mode mode, http://man7.org/linux/man-pages/man7/packet.7.html # Available modes: cpu, lb, hash, random, rollover, queue_mapping mirror_af_packet_fanout_mode = cpu # This option should be enabled if you are using Juniper with mirroring of the first X bytes of packet: maximum-packet-length 110; af_packet_read_packet_length_from_ip_header = off # Netmap traffic capture, only for FreeBSD mirror_netmap = off # Netmap based 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 not recommended for production use pcap = off # Netflow capture method with v5, v9 and IPFIX support netflow = off # sFLOW capture suitable for switches sflow = off # Configuration for Netmap, mirror, pcap, AF_XDP modes # For pcap we could specify "any" # For Netmap 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 periond (seconds) average_calculation_time = 5 # Delay between traffic recalculation attempts speed_calculation_delay = 1 # Netflow configuration # it's possible to specify multiple ports here, using commas as delimiter netflow_port = 2055 # # Netflow collector host to listen on. # # To bind on all interfaces for IPv4 and IPv6 use :: # To bind only on IPv4 use 0.0.0.0 # # To bind on localhost for IPv4 and IPv6 use ::1 # To bind only on IPv4 use 127.0.0.1 # netflow_host = 0.0.0.0 # 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 # 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 # Some vendors may lie about full packet length in sFlow packet. To avoid this issue we can switch to using IP packet length from parsed header sflow_read_packet_length_from_ip_header = off ### ### Actions when attack detected ### # This script executed for ban, unban and attack detail collection notify_script_path = /usr/local/bin/notify_about_attack.sh # collect a full dump of the attack with full payload in pcap compatible format collect_attack_pcap_dumps = 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 # 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 # GoBGP integration gobgp = off # Configuration for IPv4 announces gobgp_next_hop = 0.0.0.0 gobgp_next_hop_host_ipv4 = 0.0.0.0 gobgp_next_hop_subnet_ipv4 = 0.0.0.0 gobgp_announce_host = on gobgp_announce_whole_subnet = off gobgp_community_host = 65001:666 gobgp_community_subnet = 65001:777 # Configuration for IPv6 announces gobgp_next_hop_ipv6 = 100::1 gobgp_next_hop_host_ipv6 = 100::1 gobgp_next_hop_subnet_ipv6 = 100::1 gobgp_announce_host_ipv6 = on gobgp_announce_whole_subnet_ipv6 = off gobgp_community_host_ipv6 = 65001:666 gobgp_community_subnet_ipv6 = 65001:777 # Before using InfluxDB you need to create database using influx tool: # create database fastnetmon # InfluxDB integration # More details can be found here: https://fastnetmon.com/docs/influxdb_integration/ influxdb = off influxdb_host = 127.0.0.1 influxdb_port = 8086 influxdb_database = fastnetmon # InfluxDB auth influxdb_auth = off influxdb_user = fastnetmon influxdb_password = secure # How often we export metrics to InfluxDB influxdb_push_period = 1 # Clickhouse metrics export # Enables metrics export to Clickhouse clickhouse_metrics = off # Clickhosue database name clickhouse_metrics_database = fastnetmon # Clickhouse login clickhouse_metrics_username = default # Clickhouse password # clickhouse_metrics_password = secure-password # Clickhouse host clickhouse_metrics_host = 127.0.0.1 # Clickhouse port clickhouse_metrics_port = 9000 # Clickhouse push period, how often we export metrics to Clickhouse clickhouse_metrics_push_period = 1 # Graphite monitoring graphite = off # Please use only IP because domain names are not allowed here graphite_host = 127.0.0.1 graphite_port = 2003 # Default namespace for Graphite data graphite_prefix = fastnetmon # How often we export metrics to Graphite graphite_push_period = 1 # Add local IP addresses and aliases to monitoring list # Works only for Linux monitor_local_ip_addresses = on # Add IP addresses for OpenVZ / Virtuozzo VEs to network monitoring list monitor_openvz_vps_ip_addresses = off # 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 = 100000 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 IPv4 traffic information for fastnetmon_client cli_stats_file_path = /tmp/fastnetmon.dat # Path to file where we store IPv6 traffic information for fastnetmon_client cli_stats_ipv6_file_path = /tmp/fastnetmon_ipv6.dat # Enable gRPC API (required for fastnetmon_api_client tool) enable_api = on # Enables traffic export to Kafka kafka_traffic_export = off # Kafka traffic export topic name kafka_traffic_export_topic = fastnetmon # Kafka traffic export format: json or protobuf kafka_traffic_export_format = json # Kafka traffic export list of brokers separated by comma kafka_traffic_export_brokers = 10.154.0.1:9092,10.154.0.2:9092 # Prometheus monitoring endpoint prometheus = on # Prometheus port prometheus_port = 9209 # Prometheus host prometheus_host = 127.0.0.1 ### ### 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.2.8/src/fastnetmon.cpp000066400000000000000000002261651472727706000174010ustar00rootroot00000000000000/* Author: pavel.odintsov@gmail.com */ /* License: GPLv2 */ #include #include // We have it on all non Windows platofms #ifndef _WIN32 #include // setrlimit #endif #include "fast_library.hpp" #include "fastnetmon_types.hpp" #include "libpatricia/patricia.hpp" #include "packet_storage.hpp" // Here we store variables which differs for different paltforms #include "fast_platform.hpp" #include "fastnetmon_logic.hpp" #include "fast_endianless.hpp" #ifdef FASTNETMON_API #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif // __GNUC__ #include "abstract_subnet_counters.hpp" #include "fastnetmon_internal_api.grpc.pb.h" #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // __GNUC__ #endif // Plugins #include "netflow_plugin/netflow_collector.hpp" #ifdef ENABLE_PCAP #include "pcap_plugin/pcap_collector.hpp" #endif #include "sflow_plugin/sflow_collector.hpp" #ifdef NETMAP_PLUGIN #include "netmap_plugin/netmap_collector.hpp" #endif #ifdef FASTNETMON_ENABLE_AF_XDP #include "xdp_plugin/xdp_collector.hpp" #endif #ifdef FASTNETMON_ENABLE_AFPACKET #include "afpacket_plugin/afpacket_collector.hpp" #endif #ifdef ENABLE_GOBGP #include "actions/gobgp_action.hpp" #endif #include "bgp_protocol_flow_spec.hpp" // Yes, maybe it's not an good idea but with this we can guarantee working code in example plugin #include "example_plugin/example_collector.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "fastnetmon_configuration_scheme.hpp" #include "all_logcpp_libraries.hpp" // We do not have syslog.h on Windows #ifndef _WIN32 #include #include #endif // Boost libs #include #include #if defined(__APPLE__) #define _GNU_SOURCE #endif #include #ifdef GEOIP #include "GeoIP.h" #endif #ifdef REDIS #include #endif #include "packet_bucket.hpp" #include "ban_list.hpp" #include "metrics/graphite.hpp" #include "metrics/influxdb.hpp" // It's not enabled by default and we enable it only when we have Clickhouse libraries on platform #ifdef CLICKHOUSE_SUPPORT #include "metrics/clickhouse.hpp" #endif #ifdef KAFKA #include #endif #ifdef FASTNETMON_API #include "api.hpp" using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; using grpc::Status; std::unique_ptr api_server; bool enable_api = false; #endif #ifdef KAFKA cppkafka::Producer* kafka_traffic_export_producer = nullptr; #endif fastnetmon_configuration_t fastnetmon_global_configuration; // Traffic export to Kafka bool kafka_traffic_export = false; std::string kafka_traffic_export_topic = "fastnetmon"; kafka_traffic_export_format_t kafka_traffic_export_format = kafka_traffic_export_format_t::JSON; std::vector kafka_traffic_export_brokers; std::chrono::steady_clock::time_point last_call_of_traffic_recalculation; std::string cli_stats_file_path = "/tmp/fastnetmon.dat"; std::string cli_stats_ipv6_file_path = "/tmp/fastnetmon_ipv6.dat"; // How often we send usage data unsigned int stats_thread_sleep_time = 3600; // Delay before we send first report about usage unsigned int stats_thread_initial_call_delay = 30; std::string reporting_server = "community-stats.fastnetmon.com"; // Each this seconds we will check about available data in bucket unsigned int check_for_availible_for_processing_packets_buckets = 1; // Current time with pretty low precision, we use separate thread to update it time_t current_inaccurate_time = 0; // This is thread safe storage for captured from the wire packets for IPv4 traffic packet_buckets_storage_t packet_buckets_ipv4_storage; // This is thread safe storage for captured from the wire packets for IPv6 traffic packet_buckets_storage_t packet_buckets_ipv6_storage; unsigned int recalculate_speed_timeout = 1; // We will remove all packet buckets which runs longer than this time. This value used only for one shot buckets. // Infinite bucket's will not removed unsigned int maximum_time_since_bucket_start_to_remove = 120; FastnetmonPlatformConfigurtion fastnetmon_platform_configuration; bool notify_script_enabled = true; // We could collect attack dumps in pcap format bool collect_attack_pcap_dumps = false; bool unban_only_if_attack_finished = true; logging_configuration_t logging_configuration; // Global map with parsed config file configuration_map_t configuration_map; // Enable Prometheus bool prometheus = false; // Prometheus port unsigned short prometheus_port = 9209; // Prometheus host std::string prometheus_host = "127.0.0.1"; // 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 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; // Enable monitoring for OpenVZ VPS IP addresses by reading their list from kernel bool monitor_openvz_vps_ip_addresses = false; // We will announce whole subnet instead single IP with BGP if this flag enabled bool exabgp_announce_whole_subnet = false; std::string exabgp_command_pipe = ""; // We will announce only /32 host bool exabgp_announce_host = false; ban_settings_t global_ban_settings; // We use these flow spec rules as custom whitelist std::vector static_flowspec_based_whitelist; std::string graphite_thread_execution_time_desc = "Time consumed by pushing data to Graphite"; struct timeval graphite_thread_execution_time; // Run stats thread bool usage_stats = true; 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_connection_tracking = true; bool enable_af_xdp_collection = false; bool enable_data_collection_from_mirror = false; bool enable_netmap_collection = false; bool enable_pcap_collection = false; std::string speed_calculation_time_desc = "Time consumed by recalculation 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; std::string total_number_of_hosts_in_our_networks_desc = "Total number of hosts in our networks"; 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; // Key used for sorting clients in output. Allowed sort params: packets/bytes/flows std::string sort_parameter = "bytes"; // Number of lines in program output unsigned int max_ips_in_list = 7; // Number of lines for sending ben attack details to email unsigned int ban_details_records_count = 50; // 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(); /* Configuration block ends */ // Total IPv4 + IPv6 traffic total_speed_counters_t total_counters; // Total IPv4 traffic total_speed_counters_t total_counters_ipv4; // Total IPv6 traffic total_speed_counters_t total_counters_ipv6; std::string total_unparsed_packets_desc = "Total number of packets we failed to parse"; uint64_t total_unparsed_packets = 0; std::string total_unparsed_packets_speed_desc = "Number of packets we fail to parse per second"; uint64_t total_unparsed_packets_speed = 0; std::string total_ipv4_packets_desc = "Total number of IPv4 simple packets processed"; uint64_t total_ipv4_packets = 0; std::string total_ipv6_packets_desc = "Total number of IPv6 simple packets processed"; uint64_t total_ipv6_packets = 0; std::string unknown_ip_version_packets_desc = "Non IPv4 and non IPv6 packets"; uint64_t unknown_ip_version_packets = 0; std::string total_simple_packets_processed_desc = "Total number of simple packets processed"; uint64_t total_simple_packets_processed = 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; uint64_t total_flowspec_whitelist_packets = 0; std::string clickhouse_metrics_writes_total_desc = "Total number of Clickhouse writes"; uint64_t clickhouse_metrics_writes_total = 0; std::string clickhouse_metrics_writes_failed_desc = "Total number of failed Clickhouse writes"; uint64_t clickhouse_metrics_writes_failed = 0; // Network counters for IPv6 abstract_subnet_counters_t ipv6_network_counters; // Host counters for IPv6 abstract_subnet_counters_t ipv6_host_counters; // Here we store traffic per subnet abstract_subnet_counters_t ipv4_network_counters; // Host counters for IPv4 abstract_subnet_counters_t ipv4_host_counters; // Flow tracking structures map_of_vector_counters_for_flow_t SubnetVectorMapFlow; std::string netflow_ipfix_all_protocols_total_flows_speed_desc = "Number of IPFIX and Netflow per second"; int64_t netflow_ipfix_all_protocols_total_flows_speed = 0; std::string sflow_raw_packet_headers_total_speed_desc = "Number of sFlow headers per second"; int64_t sflow_raw_packet_headers_total_speed = 0; std::mutex flow_counter_mutex; #ifdef GEOIP map_for_counters GeoIpCounter; #endif // Banned IPv6 hosts blackhole_ban_list_t ban_list_ipv6; // Banned IPv4 hosts blackhole_ban_list_t ban_list_ipv4; 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_next_hop = ""; std::string influxdb_writes_total_desc = "Total number of InfluxDB writes"; uint64_t influxdb_writes_total = 0; std::string influxdb_writes_failed_desc = "Total number of failed InfluxDB writes"; uint64_t influxdb_writes_failed = 0; bool process_incoming_traffic = true; bool process_outgoing_traffic = true; logging_configuration_t read_logging_settings(configuration_map_t configuration_map); std::string get_amplification_attack_type(amplification_attack_type_t attack_type); ban_settings_t read_ban_settings(configuration_map_t configuration_map, std::string host_group_name = ""); bool load_configuration_file(); void free_up_all_resources(); void interruption_signal_handler(int signal_number); #ifdef FASTNETMON_API // 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() { logger << log4cpp::Priority::INFO << "Launch API server"; 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 void sigpipe_handler_for_popen(int signo) { logger << log4cpp::Priority::ERROR << "Sorry but we experienced error with popen. " << "Please check your scripts. They must receive data on stdin"; // Well, we do not need exit here because we have another options to notifying about atatck // exit(1); } #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 // 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)) { boost::algorithm::trim(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"; return; } boost::algorithm::trim(splitted_new_host_group[0]); boost::algorithm::trim(splitted_new_host_group[1]); 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_cidr_mask_t subnet; bool subnet_parse_result = convert_subnet_from_string_to_binary_with_cidr_format_safe(*itr, subnet); if (!subnet_parse_result) { logger << log4cpp::Priority::ERROR << "Cannot parse subnet " << *itr; continue; } 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(fastnetmon_platform_configuration.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; boost::algorithm::trim(line); 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) { boost::algorithm::trim(parsed_config[0]); boost::algorithm::trim(parsed_config[1]); 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_connection_tracking = true; } else { enable_connection_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) { fastnetmon_platform_configuration.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("cli_stats_ipv6_file_path") != 0) { cli_stats_ipv6_file_path = configuration_map["cli_stats_ipv6_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) { fastnetmon_global_configuration.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("speed_calculation_delay") != 0) { recalculate_speed_timeout = convert_string_to_integer(configuration_map["speed_calculation_delay"]); } if (configuration_map.count("monitor_local_ip_addresses") != 0) { monitor_local_ip_addresses = configuration_map["monitor_local_ip_addresses"] == "on" ? true : false; } if (configuration_map.count("monitor_openvz_vps_ip_addresses") != 0) { monitor_openvz_vps_ip_addresses = configuration_map["monitor_openvz_vps_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) { fastnetmon_global_configuration.gobgp = 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 (exabgp_enabled) { logger << log4cpp::Priority::INFO << "ExaBGP support initialized correctly"; } } // sFlow section if (configuration_map.count("sflow") != 0) { if (configuration_map["sflow"] == "on") { fastnetmon_global_configuration.sflow = true; } else { fastnetmon_global_configuration.sflow = false; } } if (configuration_map.count("sflow_host") != 0) { fastnetmon_global_configuration.sflow_host = configuration_map["sflow_host"]; } if (configuration_map.count("sflow_read_packet_length_from_ip_header") != 0) { fastnetmon_global_configuration.sflow_read_packet_length_from_ip_header = configuration_map["sflow_read_packet_length_from_ip_header"] == "on"; } // Read sFlow ports std::string sflow_ports_string = ""; // Please note that it differs from field name in Advanced edition which uses "sflow_ports" if (configuration_map.count("sflow_port") != 0) { sflow_ports_string = configuration_map["sflow_port"]; } std::vector sflow_ports_for_listen; boost::split(sflow_ports_for_listen, sflow_ports_string, boost::is_any_of(","), boost::token_compress_on); std::vector sflow_ports; for (auto port_string : sflow_ports_for_listen) { unsigned int sflow_port = convert_string_to_integer(port_string); if (sflow_port == 0) { logger << log4cpp::Priority::ERROR << "Cannot parse sFlow port: " << port_string; continue; } fastnetmon_global_configuration.sflow_ports.push_back(sflow_port); } logger << log4cpp::Priority::INFO << "We parsed " << fastnetmon_global_configuration.sflow_ports.size() << " ports for sFlow"; // Netflow if (configuration_map.count("netflow") != 0) { if (configuration_map["netflow"] == "on") { fastnetmon_global_configuration.netflow = true; } else { fastnetmon_global_configuration.netflow = false; } } if (configuration_map.count("netflow_host") != 0) { fastnetmon_global_configuration.netflow_host = configuration_map["netflow_host"]; } // Netflow ports std::string netflow_ports_string = ""; if (configuration_map.count("netflow_port") != 0) { netflow_ports_string = configuration_map["netflow_port"]; } if (configuration_map.count("netflow_sampling_ratio") != 0) { fastnetmon_global_configuration.netflow_sampling_ratio = convert_string_to_integer(configuration_map["netflow_sampling_ratio"]); logger << log4cpp::Priority::INFO << "Using custom sampling ratio for Netflow v9 and IPFIX: " << fastnetmon_global_configuration.netflow_sampling_ratio; } std::vector ports_for_listen; boost::split(ports_for_listen, netflow_ports_string, boost::is_any_of(","), boost::token_compress_on); std::vector netflow_ports; for (auto port : ports_for_listen) { unsigned int netflow_port = convert_string_to_integer(port); if (netflow_port == 0) { logger << log4cpp::Priority::ERROR << "Cannot parse Netflow port: " << port; continue; } fastnetmon_global_configuration.netflow_ports.push_back(netflow_port); } 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; } // Graphite if (configuration_map.count("graphite") != 0) { fastnetmon_global_configuration.graphite = configuration_map["graphite"] == "on" ? true : false; } if (configuration_map.count("graphite_host") != 0) { fastnetmon_global_configuration.graphite_host = configuration_map["graphite_host"]; } if (configuration_map.count("graphite_port") != 0) { fastnetmon_global_configuration.graphite_port = convert_string_to_integer(configuration_map["graphite_port"]); } if (configuration_map.count("graphite_push_period") != 0) { fastnetmon_global_configuration.graphite_push_period = convert_string_to_integer(configuration_map["graphite_push_period"]); } // InfluxDB if (configuration_map.count("influxdb") != 0) { fastnetmon_global_configuration.influxdb = configuration_map["influxdb"] == "on" ? true : false; } if (configuration_map.count("influxdb_port") != 0) { fastnetmon_global_configuration.influxdb_port = convert_string_to_integer(configuration_map["influxdb_port"]); } if (configuration_map.count("influxdb_push_period") != 0) { fastnetmon_global_configuration.influxdb_push_period = convert_string_to_integer(configuration_map["influxdb_push_period"]); } if (configuration_map.count("influxdb_host") != 0) { fastnetmon_global_configuration.influxdb_host = configuration_map["influxdb_host"]; } if (configuration_map.count("influxdb_database") != 0) { fastnetmon_global_configuration.influxdb_database = configuration_map["influxdb_database"]; } if (configuration_map.count("influxdb_auth") != 0) { fastnetmon_global_configuration.influxdb_auth = configuration_map["influxdb_auth"] == "on" ? true : false; } if (configuration_map.count("influxdb_user") != 0) { fastnetmon_global_configuration.influxdb_user = configuration_map["influxdb_user"]; } if (configuration_map.count("influxdb_password") != 0) { fastnetmon_global_configuration.influxdb_password = configuration_map["influxdb_password"]; } // Clickhouse if (configuration_map.contains("clickhouse_metrics")) { fastnetmon_global_configuration.clickhouse_metrics = configuration_map["clickhouse_metrics"] == "on" ? true : false; } if (configuration_map.contains("clickhouse_metrics_database")) { fastnetmon_global_configuration.clickhouse_metrics_database = configuration_map["clickhouse_metrics_database"]; } if (configuration_map.contains("clickhouse_metrics_username")) { fastnetmon_global_configuration.clickhouse_metrics_username = configuration_map["clickhouse_metrics_username"]; } if (configuration_map.contains("clickhouse_metrics_password")) { fastnetmon_global_configuration.clickhouse_metrics_password = configuration_map["clickhouse_metrics_password"]; } if (configuration_map.contains("clickhouse_metrics_host")) { fastnetmon_global_configuration.clickhouse_metrics_host = configuration_map["clickhouse_metrics_host"]; } if (configuration_map.contains("clickhouse_metrics_port") != 0) { fastnetmon_global_configuration.clickhouse_metrics_port = convert_string_to_integer(configuration_map["clickhouse_metrics_port"]); } if (configuration_map.contains("clickhouse_metrics_push_period") != 0) { fastnetmon_global_configuration.clickhouse_metrics_push_period = convert_string_to_integer(configuration_map["clickhouse_metrics_push_period"]); } 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_afxdp") != 0) { if (configuration_map["mirror_afxdp"] == "on") { enable_af_xdp_collection = true; } else { enable_af_xdp_collection = false; } } if (configuration_map.count("mirror_netmap") != 0) { if (configuration_map["mirror_netmap"] == "on") { enable_netmap_collection = true; } else { enable_netmap_collection = false; } } // AF_PACKET Mirror if (configuration_map.count("mirror_afpacket") != 0) { fastnetmon_global_configuration.mirror_afpacket = configuration_map["mirror_afpacket"] == "on"; } std::string interfaces_list; if (configuration_map.count("interfaces") != 0) { interfaces_list = configuration_map["interfaces"]; std::vector interfaces_for_listen; boost::split(fastnetmon_global_configuration.interfaces, interfaces_list, boost::is_any_of(","), boost::token_compress_on); } // Please note that field name does not match name of configuration option if (configuration_map.count("af_packet_read_packet_length_from_ip_header") != 0) { fastnetmon_global_configuration.af_packet_read_packet_length_from_ip_header = configuration_map["af_packet_read_packet_length_from_ip_header"] == "on"; } if (configuration_map.count("mirror_af_packet_fanout_mode") != 0) { fastnetmon_global_configuration.mirror_af_packet_fanout_mode = configuration_map["mirror_af_packet_fanout_mode"] == "on"; } // XDP if (fastnetmon_global_configuration.mirror_afpacket && enable_af_xdp_collection) { logger << log4cpp::Priority::ERROR << "You cannot use AF_XDP and AF_PACKET in same time, select one"; exit(1); } 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 (auto hostgroup_itr = host_groups.begin(); hostgroup_itr != host_groups.end(); ++hostgroup_itr) { std::string host_group_name = hostgroup_itr->first; logger << log4cpp::Priority::DEBUG << "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::DEBUG << "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) { fastnetmon_platform_configuration.white_list_path = configuration_map["white_list_path"]; } if (configuration_map.count("networks_list_path") != 0) { fastnetmon_platform_configuration.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 if (configuration_map.count("prometheus") != 0) { if (configuration_map["prometheus"] == "on") { prometheus = true; } } if (configuration_map.count("prometheus_host") != 0) { prometheus_host = configuration_map["prometheus_host"]; } if (configuration_map.count("prometheus_port") != 0) { prometheus_port = convert_string_to_integer(configuration_map["prometheus_port"]); } #ifdef KAFKA if (configuration_map.count("kafka_traffic_export") != 0) { if (configuration_map["kafka_traffic_export"] == "on") { kafka_traffic_export = true; } } if (configuration_map.count("kafka_traffic_export_topic") != 0) { kafka_traffic_export_topic = configuration_map["kafka_traffic_export_topic"]; } // Load brokers list if (configuration_map.count("kafka_traffic_export_brokers") != 0) { std::string brokers_list_raw = configuration_map["kafka_traffic_export_brokers"]; boost::split(kafka_traffic_export_brokers, brokers_list_raw, boost::is_any_of(","), boost::token_compress_on); } if (configuration_map.count("kafka_traffic_export_format") != 0) { std::string kafka_traffic_export_format_raw = configuration_map["kafka_traffic_export_format"]; // Switch it to lowercase boost::algorithm::to_lower(kafka_traffic_export_format_raw); if (kafka_traffic_export_format_raw == "json") { kafka_traffic_export_format = kafka_traffic_export_format_t::JSON; } else if (kafka_traffic_export_format_raw == "protobuf") { kafka_traffic_export_format = kafka_traffic_export_format_t::Protobuf; } else { logger << log4cpp::Priority::ERROR << "Unknown format for kafka_traffic_export_format: " << kafka_traffic_export_format_raw; kafka_traffic_export_format = kafka_traffic_export_format_t::Unknown; } } #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) { fastnetmon_platform_configuration.notify_script_path = configuration_map["notify_script_path"]; } if (file_exists(fastnetmon_platform_configuration.notify_script_path)) { notify_script_enabled = true; } else { logger << log4cpp::Priority::ERROR << "We can't find notify script " << fastnetmon_platform_configuration.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("dump_all_traffic") != 0) { DEBUG_DUMP_ALL_PACKETS = configuration_map["dump_all_traffic"] == "on" ? true : false; } if (configuration_map.count("dump_other_traffic") != 0) { DEBUG_DUMP_OTHER_PACKETS = configuration_map["dump_other_traffic"] == "on" ? true : false; } return true; } // Enable core dumps for simplify debug tasks #ifndef _WIN32 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); } } #endif 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: // "< network_list_from_config = read_file_to_vector(fastnetmon_platform_configuration.white_list_path); uint32_t ipv4_whitelists = 0; uint32_t ipv6_whitelists = 0; for (const auto& subnet_raw : network_list_from_config) { // Trim leading and trailing spaces std::string subnet = boost::algorithm::trim_copy(subnet_raw); if (subnet.empty()) { continue; } // Ignore comments if (subnet.find("#") == 0) { continue; } if (strstr(subnet.c_str(), ":") == NULL) { if (!is_cidr_subnet(subnet)) { logger << log4cpp::Priority::ERROR << "Cannot parse " << subnet << " as IPv4 prefix"; continue; } // IPv4 ipv4_whitelists++; make_and_lookup(whitelist_tree_ipv4, subnet.c_str()); } else { // IPv6 // Verify IPv6 prefix format subnet_ipv6_cidr_mask_t ipv6_subnet; if (!read_ipv6_subnet_from_string(ipv6_subnet, subnet)) { logger << log4cpp::Priority::ERROR << "Cannot parse " << subnet << " as IPv6 prefix"; continue; } ipv6_whitelists++; make_and_lookup_ipv6(whitelist_tree_ipv6, subnet.c_str()); } } logger << log4cpp::Priority::INFO << "We loaded " << ipv4_whitelists << " IPv4 networks and " << ipv6_whitelists << " IPv6 networks from whitelist"; } std::vector networks_list_ipv4_as_string; std::vector networks_list_ipv6_as_string; // We can build list of our subnets automatically here if (monitor_openvz_vps_ip_addresses && 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 << "On Linux we can use ip tool to detect local IPs"; ip_addresses_list_t ip_list = get_local_ip_v4_addresses_list(); logger << log4cpp::Priority::INFO << "We found " << ip_list.size() << " local IP addresses"; 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(fastnetmon_platform_configuration.networks_list_path)) { std::vector network_list_from_config = read_file_to_vector(fastnetmon_platform_configuration.networks_list_path); for (const auto& subnet_raw : network_list_from_config) { // Trim leading and trailing spaces std::string subnet = boost::algorithm::trim_copy(subnet_raw); if (subnet.empty()) { // Empty line continue; } if (subnet.length() == 0) { // Skip blank lines in subnet list file silently continue; } // Ignore comments if (subnet.find("#") == 0) { continue; } if (strstr(subnet.c_str(), ":") == NULL) { networks_list_ipv4_as_string.push_back(subnet); } else { networks_list_ipv6_as_string.push_back(subnet); } } logger << log4cpp::Priority::INFO << "We loaded " << network_list_from_config.size() << " networks from networks file"; } 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)) { 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 = 0; bool ip_parser_result = convert_ip_as_string_to_uint_safe(network_address, subnet_address_as_uint); if (!ip_parser_result) { logger << log4cpp::Priority::ERROR << "Cannot parse " << network_address << " as IP"; continue; } 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, 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(subnet_counter_t) * 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, subnet_vectors_allocator); logger << log4cpp::Priority::INFO << "We loaded " << networks_list_ipv4_as_string.size() << " IPv4 subnets to our in-memory list of networks"; return true; } #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_ipv4_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_ipv4_program(); } } // It's vizualization thread :) void screen_draw_ipv6_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_ipv6_program(); } } 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(); } } 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(bool log_to_console) { logger.setPriority(log4cpp::Priority::INFO); // In this case we log everything to console if (log_to_console) { log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("[%p] %m%n"); // We duplicate stdout because it will be closed by log4cpp on object termination and we do not need it log4cpp::Appender* console_appender = new log4cpp::FileAppender("stdout", ::dup(fileno(stdout))); console_appender->setLayout(layout); logger.addAppender(console_appender); } else { log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("%d [%p] %m%n"); // 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(fastnetmon_platform_configuration.log_file_path)) { std::cerr << "Can't open log file " << fastnetmon_platform_configuration.log_file_path << " for writing! Please check file and folder permissions" << std::endl; exit(EXIT_FAILURE); } log4cpp::Appender* appender = new log4cpp::FileAppender("default", fastnetmon_platform_configuration.log_file_path); appender->setLayout(layout); logger.addAppender(appender); } logger << log4cpp::Priority::INFO << "Logger initialized"; } void reconfigure_logging_level(const std::string& logging_level) { // Configure logging level log4cpp::Priority::Value priority = log4cpp::Priority::INFO; if (logging_level == "debug") { priority = log4cpp::Priority::DEBUG; logger << log4cpp::Priority::DEBUG << "Setting logging level to debug"; } else if (logging_level == "info" || logging_level == "") { // It may be set to empty value in old versions before we introduced this flag logger << log4cpp::Priority::DEBUG << "Setting logging level to info"; priority = log4cpp::Priority::INFO; } else { logger << log4cpp::Priority::ERROR << "Unknown logging level: " << logging_level; } logger.setPriority(priority); } void reconfigure_logging() { log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("[%p] %m%n"); if (logging_configuration.local_syslog_logging) { #ifdef _WIN32 logger << log4cpp::Priority::ERROR << "Local syslog logging is not supported on Windows platform"; #else log4cpp::Appender* local_syslog_appender = new log4cpp::SyslogAppender("fastnetmon", "fastnetmon", LOG_USER); local_syslog_appender->setLayout(layout); logger.addAppender(local_syslog_appender); logger << log4cpp::Priority::INFO << "We start local syslog logging corectly"; #endif } if (logging_configuration.remote_syslog_logging) { #ifdef _WIN32 logger << log4cpp::Priority::ERROR << "Remote syslog logging is not supported on Windows platform"; #else log4cpp::Appender* remote_syslog_appender = new log4cpp::RemoteSyslogAppender("fastnetmon", "fastnetmon", logging_configuration.remote_syslog_server, LOG_USER, logging_configuration.remote_syslog_port); remote_syslog_appender->setLayout(layout); logger.addAppender(remote_syslog_appender); #endif logger << log4cpp::Priority::INFO << "We start remote syslog logging correctly"; } reconfigure_logging_level(logging_configuration.logging_level); } #ifndef _WIN32 // Call fork function // We have no work on Windows 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 program _exit(0); } return status; } #endif 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 // Ignore warning because I prefer to have these unusued variables here for clarity #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" int first_dup_result = dup(0); int second_dup_result = dup(0); #pragma GCC diagnostic pop } // Handles fatal failure of FastNetMon's daemon void fatal_signal_handler(int signum) { ::signal(signum, SIG_DFL); boost::stacktrace::safe_dump_to(fastnetmon_platform_configuration.backtrace_path.c_str()); ::raise(SIGABRT); } int main(int argc, char** argv) { bool daemonize = false; bool only_configuration_check = false; namespace po = boost::program_options; // Switch logging to console bool log_to_console = false; // This was legacy logic for init V based distros to prevent multiple copies of same daemon running in same time bool do_pid_checks = false; try { // clang-format off po::options_description desc("Allowed options"); desc.add_options() ("help", "produce help message") ("version", "show version") ("daemonize", "detach from the terminal") ("configuration_check", "check configuration and exit") ("configuration_file", po::value(),"set path to custom configuration file") ("log_file", po::value(), "set path to custom log file") ("log_to_console", "switches all logging to console") ("pid_logic", "Enables logic which stores PID to file and uses it for duplicate instance checks") ("disable_pid_logic", "Disables logic which stores PID to file and uses it for duplicate instance checks. No op as it's disabled by default"); // clang-format on 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_platform_configuration.fastnetmon_version << std::endl; exit(EXIT_SUCCESS); } if (vm.count("daemonize")) { daemonize = true; } if (vm.count("configuration_check")) { only_configuration_check = true; } if (vm.count("configuration_file")) { fastnetmon_platform_configuration.global_config_path = vm["configuration_file"].as(); std::cout << "We will use custom path to configuration file: " << fastnetmon_platform_configuration.global_config_path << std::endl; } if (vm.count("log_file")) { fastnetmon_platform_configuration.log_file_path = vm["log_file"].as(); std::cout << "We will use custom path to log file: " << fastnetmon_platform_configuration.log_file_path << std::endl; } if (vm.count("log_to_console")) { std::cout << "We will log everything on console" << std::endl; log_to_console = true; } // No op as it's disabled by default if (vm.count("disable_pid_logic")) { do_pid_checks = false; } if (vm.count("pid_logic")) { do_pid_checks = true; } } 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 #ifndef _WIN32 if (daemonize) { int status = 0; std::cout << "We will run in daemonized mode" << std::endl; 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 // I prefer to keep this variable for clarity #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" int chdir_result = chdir("/"); #pragma GCC diagnostic pop // close all descriptors because we are daemon! redirect_fds(); } } #else if (daemonize) { std::cerr << "ERROR: " << "Daemon mode is not supported on Windows platforms" << std::endl; exit(EXIT_FAILURE); } #endif // Enable core dumps #ifndef _WIN32 enable_core_dumps(); #endif // Setup fatal signal handlers to gracefully capture them ::signal(SIGSEGV, &fatal_signal_handler); ::signal(SIGABRT, &fatal_signal_handler); init_logging(log_to_console); if (std::filesystem::exists(fastnetmon_platform_configuration.backtrace_path)) { // there is a backtrace std::ifstream ifs(fastnetmon_platform_configuration.backtrace_path); boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump(ifs); logger << log4cpp::Priority::ERROR << "Previous run crashed, you can find stack trace below"; logger << log4cpp::Priority::ERROR << st; // cleaning up ifs.close(); std::filesystem::remove(fastnetmon_platform_configuration.backtrace_path); } // 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 " << fastnetmon_platform_configuration.global_config_path << " please create it!" << std::endl; exit(1); } if (only_configuration_check) { logger << log4cpp::Priority::INFO << "Configuration file is correct. Shutdown toolkit"; exit(0); } // On Linux and FreeBSD platforms we use kill to check that process with specific PID is alive // Unfortunately, it's way more tricky to implement such approach on Windows and we decided just to disable this logic #ifdef _WIN32 if (do_pid_checks) { logger << log4cpp::Priority::INFO << "PID logic is not available on Windows"; exit(1); } #else if (do_pid_checks && file_exists(fastnetmon_platform_configuration.pid_path)) { pid_t pid_from_file = 0; if (read_pid_from_file(pid_from_file, fastnetmon_platform_configuration.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 (do_pid_checks) { // If we not failed in check steps we could run toolkit bool print_pid_to_file_result = print_pid_to_file(getpid(), fastnetmon_platform_configuration.pid_path); if (!print_pid_to_file_result) { logger << log4cpp::Priority::ERROR << "Could not create pid file, please check permissions: " << fastnetmon_platform_configuration.pid_path; exit(EXIT_FAILURE); } } #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); /* Create folder for attack details */ if (!folder_exists(fastnetmon_platform_configuration.attack_details_folder)) { logger << log4cpp::Priority::ERROR << "Folder for attack details does not exist: " << fastnetmon_platform_configuration.attack_details_folder; } 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_t) != sizeof(uint64_t) or sizeof(packed_conntrack_hash_t) != 8) { logger << log4cpp::Priority::INFO << "Assertion about size of packed_conntrack_hash, it's " << sizeof(packed_conntrack_hash_t) << " 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(); load_whitelist_rules(); // We should specify size of circular buffers here packet_buckets_ipv4_storage.set_buffers_capacity(ban_details_records_count); // Set capacity for nested buffers packet_buckets_ipv6_storage.set_buffers_capacity(ban_details_records_count); // Setup CTRL+C handler if (signal(SIGINT, interruption_signal_handler) == SIG_ERR) { logger << log4cpp::Priority::ERROR << "Can't setup SIGINT handler"; exit(1); } // Windows does not support SIGPIPE #ifndef _WIN32 /* 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); } #endif #ifdef GEOIP // Init GeoIP if (!geoip_init()) { logger << log4cpp::Priority::ERROR << "Can't load geoip tables"; exit(1); } #endif // Init previous run date last_call_of_traffic_recalculation = std::chrono::steady_clock::now(); // We call init for each action #ifdef ENABLE_GOBGP if (fastnetmon_global_configuration.gobgp) { gobgp_action_init(); } #endif #ifdef KAFKA if (kafka_traffic_export) { if (kafka_traffic_export_brokers.size() == 0) { logger << log4cpp::Priority::ERROR << "Kafka traffic export requires at least single broker, please configure kafka_traffic_export_brokers"; } else { std::string all_brokers = boost::algorithm::join(kafka_traffic_export_brokers, ","); std::string partitioner = "random"; // All available configuration options: https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md cppkafka::Configuration kafka_traffic_export_config = { { "metadata.broker.list", all_brokers }, { "request.required.acks", "0" }, // Disable ACKs { "partitioner", partitioner }, }; logger << log4cpp::Priority::INFO << "Initialise Kafka producer for traffic export"; // In may crash during producer creation try { kafka_traffic_export_producer = new cppkafka::Producer(kafka_traffic_export_config); } catch (...) { logger << log4cpp::Priority::ERROR << "Cannot initialise Kafka producer"; kafka_traffic_export = false; } logger << log4cpp::Priority::INFO << "Kafka traffic producer is ready"; } } #endif #ifdef FASTNETMON_API if (enable_api) { service_thread_group.add_thread(new boost::thread(RunApiServer)); } #endif if (prometheus) { auto prometheus_thread = new boost::thread(start_prometheus_web_server); set_boost_process_name(prometheus_thread, "prometheus"); service_thread_group.add_thread(prometheus_thread); } // Set inaccurate time value which will be used in process_packet() from capture backends time(¤t_inaccurate_time); // start thread which pre-calculates speed for system counters auto system_counters_speed_thread = new boost::thread(system_counters_speed_thread_handler); set_boost_process_name(system_counters_speed_thread, "metrics_speed"); service_thread_group.add_thread(system_counters_speed_thread); auto inaccurate_time_generator_thread = new boost::thread(inaccurate_time_generator); set_boost_process_name(inaccurate_time_generator_thread, "fast_time"); service_thread_group.add_thread(inaccurate_time_generator_thread); if (configuration_map.count("disable_usage_report") != 0 && configuration_map["disable_usage_report"] == "on") { usage_stats = false; } if (usage_stats) { auto stats_thread = new boost::thread(collect_stats); set_boost_process_name(stats_thread, "stats"); service_thread_group.add_thread(stats_thread); } // Run screen draw thread for IPv4 service_thread_group.add_thread(new boost::thread(screen_draw_ipv4_thread)); // Run screen draw thread for IPv6 service_thread_group.add_thread(new boost::thread(screen_draw_ipv6_thread)); // Graphite export thread if (fastnetmon_global_configuration.graphite) { service_thread_group.add_thread(new boost::thread(graphite_push_thread)); } // InfluxDB export thread if (fastnetmon_global_configuration.influxdb) { service_thread_group.add_thread(new boost::thread(influxdb_push_thread)); } #ifdef CLICKHOUSE_SUPPORT // Clickhouse metrics export therad if (fastnetmon_global_configuration.clickhouse_metrics) { logger << log4cpp::Priority::INFO << "Starting Clickhouse metrics export thread"; service_thread_group.add_thread(new boost::thread(clickhouse_push_thread)); } #endif // 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)); } // This thread will check about filled buckets with packets and process they auto check_traffic_buckets_thread = new boost::thread(check_traffic_buckets); set_boost_process_name(check_traffic_buckets_thread, "check_buckets"); service_thread_group.add_thread(check_traffic_buckets_thread); #ifdef NETMAP_PLUGIN // netmap processing if (enable_netmap_collection) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_netmap_collection, process_packet)); } #endif #ifdef FASTNETMON_ENABLE_AFPACKET if (fastnetmon_global_configuration.mirror_afpacket) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_afpacket_collection, process_packet)); } #endif #ifdef FASTNETMON_ENABLE_AF_XDP if (enable_af_xdp_collection) { auto xdp_thread = new boost::thread(start_xdp_collection, process_packet); set_boost_process_name(xdp_thread, "xdp"); packet_capture_plugin_thread_group.add_thread(xdp_thread); } #endif if (fastnetmon_global_configuration.sflow) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_sflow_collection, process_packet)); } if (fastnetmon_global_configuration.netflow) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_netflow_collection, process_packet)); } #ifdef ENABLE_PCAP if (enable_pcap_collection) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_pcap_collection, process_packet)); } #endif // 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); Destroy_Patricia(whitelist_tree_ipv4); Destroy_Patricia(lookup_tree_ipv6); Destroy_Patricia(whitelist_tree_ipv6); } // For correct program 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); } fastnetmon-1.2.8/src/fastnetmon.service.in000066400000000000000000000007341472727706000206540ustar00rootroot00000000000000[Unit] Description=FastNetMon - DoS/DDoS analyzer with sFlow/Netflow/mirror support Documentation=man:fastnetmon(8) After=network.target remote-fs.target [Service] Type=simple ExecStart=@CMAKE_INSTALL_SBINDIR@/fastnetmon --log_to_console User=fastnetmon Group=fastnetmon Restart=on-failure RestartSec=3 LimitNOFILE=65535 # We need it to use AF_PACKET and AF_XDP when run under non root user AmbientCapabilities=CAP_NET_RAW CAP_IPC_LOCK [Install] WantedBy=multi-user.target fastnetmon-1.2.8/src/fastnetmon_actions.hpp000066400000000000000000000003461472727706000211150ustar00rootroot00000000000000#include "all_logcpp_libraries.hpp" #include "fast_library.hpp" // Get log4cpp logger from main program extern log4cpp::Category& logger; // Global configuration map extern std::map configuration_map; fastnetmon-1.2.8/src/fastnetmon_api_client.cpp000066400000000000000000000101611472727706000215530ustar00rootroot00000000000000#include #include #include #include #include "fastnetmon_internal_api.grpc.pb.h" using fastnetmoninternal::BanListReply; using fastnetmoninternal::BanListRequest; using fastnetmoninternal::Fastnetmon; using grpc::Channel; using grpc::ClientContext; using grpc::Status; 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; fastnetmoninternal::ExecuteBanRequest request; fastnetmoninternal::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 << "Query 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 << "Query failed " + status.error_message() << std::endl; return; } } } private: std::unique_ptr stub_; }; int main(int argc, char** argv) { std::string supported_commands_list = "ban, unban, get_banlist"; if (argc <= 1) { std::cerr << "Please provide command as argument, supported commands: " << supported_commands_list << std::endl; return 1; } // 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::InsecureChannelCredentials())); std::string request_command = argv[1]; if (request_command == "get_banlist") { fastnetmon.GetBanList(); } else if (request_command == "ban" or request_command == "unban") { if (argc < 3) { 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 if (request_command == "help" || request_command == "--help") { std::cout << "Supported commands: " << supported_commands_list; return 0; } else { std::cerr << "Unknown command " << request_command << " we support only: " << supported_commands_list << std::endl; return 1; } return 0; } fastnetmon-1.2.8/src/fastnetmon_client.cpp000066400000000000000000000055221472727706000207270ustar00rootroot00000000000000#include #include #include #include #include #include #ifdef _WIN32 // msys2 and mingw use nested path for some reasons but Linux keeps it in include directly: https://packages.msys2.org/package/mingw-w64-x86_64-ncurses // On Windows we do only static builds to avoid carrying bunch of dlls with us #define NCURSES_STATIC #include #else #include #endif #include std::string cli_stats_ipv4_file_path = "/tmp/fastnetmon.dat"; std::string cli_stats_ipv6_file_path = "/tmp/fastnetmon_ipv6.dat"; int main(int argc, char** argv) { bool ipv6_mode = false; namespace po = boost::program_options; try { // clang-format off po::options_description desc("Allowed options"); desc.add_options() ("help", "produce help message") ("ipv6", "switch to IPv6 mode"); // clang-format on 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("ipv6")) { ipv6_mode = true; } } catch (po::error& e) { std::cerr << "ERROR: " << e.what() << std::endl << std::endl; exit(EXIT_FAILURE); } // Init ncurses screen initscr(); // disable any character output noecho(); // hide cursor curs_set(0); // Do not wait for getch timeout(0); while (true) { std::this_thread::sleep_for (std::chrono::seconds(1)); // clean up screen clear(); int c = getch(); if (c == 'q') { endwin(); exit(0); } std::string cli_stats_file_path = cli_stats_ipv4_file_path; if (ipv6_mode) { cli_stats_file_path = cli_stats_ipv6_file_path; } 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::string error_message = "Can't open fastnetmon stats file: " + cli_stats_file_path; addstr(error_message.c_str()); // update screen refresh(); continue; } std::string line = ""; std::stringstream screen_buffer; while (getline(reading_file, line)) { screen_buffer << line << "\n"; } reading_file.close(); addstr(screen_buffer.str().c_str()); // update screen refresh(); } /* End ncurses mode */ endwin(); } fastnetmon-1.2.8/src/fastnetmon_configuration_scheme.hpp000066400000000000000000000053211472727706000236460ustar00rootroot00000000000000#pragma once #include #include #include #include #include "fastnetmon_networks.hpp" class fastnetmon_configuration_t { public: // sFlow bool sflow{ false }; std::vector sflow_ports{}; std::string sflow_host{ "0.0.0.0" }; bool sflow_read_packet_length_from_ip_header{ false }; bool sflow_extract_tunnel_traffic{ false }; // Netflow / IPFIX bool netflow{ false }; std::vector netflow_ports{}; std::string netflow_host{ "0.0.0.0" }; unsigned int netflow_sampling_ratio{ 1 }; // Mirror AF_PACKET bool mirror_afpacket{ false }; std::vector interfaces{}; bool afpacket_strict_cpu_affinity{ false }; std::string mirror_af_packet_fanout_mode{ "cpu" }; bool af_packet_read_packet_length_from_ip_header{ false }; bool af_packet_extract_tunnel_traffic{ false }; bool afpacket_execute_strict_cpu_affinity{ false }; // Clickhouse metrics bool clickhouse_metrics{ false }; std::string clickhouse_metrics_database{ "fastnetmon" }; std::string clickhouse_metrics_username{ "default" }; std::string clickhouse_metrics_password{ "" }; std::string clickhouse_metrics_host{ "127.0.0.1" }; unsigned int clickhouse_metrics_port{ 9000 }; unsigned int clickhouse_metrics_push_period{ 1 }; // InfluxDB metrics bool influxdb{ false }; std::string influxdb_database{ "fastnetmon" }; std::string influxdb_host{ "127.0.0.1" }; unsigned int influxdb_port{ 8086 }; std::string influxdb_user{ "fastnetmon" }; std::string influxdb_password{ "fastnetmon" }; bool influxdb_auth{ false }; unsigned int influxdb_push_period{ 1 }; // Graphtie metrics bool graphite{ false }; std::string graphite_host{ "127.0.0.1" }; unsigned int graphite_port{ 2003 }; std::string graphite_prefix{ "fastnetmon" }; unsigned int graphite_push_period{ 1 }; // GoBGP bool gobgp{ false }; // IPv4 bool gobgp_announce_host{ false }; bool gobgp_announce_whole_subnet{ false }; std::string gobgp_community_host{ "65001:668" }; std::string gobgp_community_subnet{ "65001:667" }; std::string gobgp_next_hop{ "0.0.0.0" }; std::string gobgp_next_hop_host_ipv4{ "0.0.0.0" }; std::string gobgp_next_hop_subnet_ipv4{ "0.0.0.0" }; // IPv6 bool gobgp_announce_host_ipv6{ false }; bool gobgp_announce_whole_subnet_ipv6{ false }; std::string gobgp_next_hop_ipv6{ "100::1" }; std::string gobgp_next_hop_host_ipv6{ "::0" }; std::string gobgp_next_hop_subnet_ipv6{ "::0" }; std::string gobgp_community_host_ipv6{ "65001:668" }; std::string gobgp_community_subnet_ipv6{ "65001:667" }; }; fastnetmon-1.2.8/src/fastnetmon_install.pl000077500000000000000000000007261472727706000207540ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; print "Hello!\n\n"; print "Thank you for interest in FastNetMon Community\n"; print "We've moved to new installer tool documented here: https://fastnetmon.com/install/\n"; print "\n"; print "To start installation procedure please do following: \n\n"; print "wget https://install.fastnetmon.com/installer -Oinstaller\n"; print "sudo chmod +x installer\n"; print "sudo ./installer -install_community_edition\n"; print "\n"; fastnetmon-1.2.8/src/fastnetmon_internal_api.proto000066400000000000000000000125321472727706000224760ustar00rootroot00000000000000syntax = "proto3"; package fastnetmoninternal; option go_package = "./;fastnetmoninternal"; service Fastnetmon { // TODO: legacy to remove and replace by DisableMitigation rpc ExecuteUnBan(ExecuteBanRequest) returns (ExecuteBanReply) {} // Pings gRPC server to check availability rpc Ping(PingRequest) returns (PingReply) {} // Returns current running version of FastNetMon rpc GetRunningVersion(GetRunningVersionRequest) returns (GetRunningVersionReply) {} // Get standard blacklist rpc GetBanlist(BanListRequest) returns (stream BanListReply) {} // Block hosts rpc ExecuteBan(ExecuteBanRequest) returns (ExecuteBanReply) {} // For local hosts rpc DisableMitigation(DisableMitigationRequest) returns (DisableMitigationReply) {} // This method will return total counters rpc GetTotalTrafficCounters(GetTotalTrafficCountersRequest) returns (stream SixtyFourNamedCounter) {} // This method will return total counters only for IPv4 traffic rpc GetTotalTrafficCountersV4(GetTotalTrafficCountersRequest) returns (stream SixtyFourNamedCounter) {} // This method will return total counters only for IPv6 traffic rpc GetTotalTrafficCountersV6(GetTotalTrafficCountersRequest) returns (stream SixtyFourNamedCounter) {} // This method will return arbitrary counters related with FastNetMon internals rpc GetSystemCounters(GetSystemCountersRequest) returns (stream SystemCounter) {} // Return per subnet traffic stats rpc GetNetworkCounters(GetNetworkCountersRequest) returns (stream NetworkCounter) {} // Return per subnet IPv6 traffic stats rpc GetNetworkCountersV6(GetNetworkCountersV6Request) returns (stream NetworkCounter) {} // Return per host IPv4 traffic stats rpc GetHostCountersV4(GetHostCountersRequest) returns (stream HostCounter) {} // Return per IPv6 host traffic stats rpc GetHostCountersV6(GetHostCountersV6Request) returns (stream HostCounter) {} } // We will reuse these enums all the way around enum OrderingType { BYTES = 0; PACKETS = 1; FLOWS = 2; } enum OrderingDirection { INCOMING = 0; OUTGOING = 1; } message GetRunningVersionRequest { } message GetRunningVersionReply { string version_main = 1; string version_git = 2; } message PingRequest { } message PingReply { } message HostGroup { string host_group_name = 1; } message GetSystemCountersRequest { }; message Network { string network = 1; }; message GetNetworkCountersV6Request { OrderingDirection order_by_direction = 1; OrderingType order_by = 2; } message GetNetworkCountersRequest { OrderingDirection order_by_direction = 1; OrderingType order_by = 2; } message GetHostCountersV6Request { OrderingDirection order_by_direction = 1; OrderingType order_by = 2; uint32 number_of_hosts = 3; } message GetHostCountersRequest { OrderingDirection order_by_direction = 1; OrderingType order_by = 2; uint32 number_of_hosts = 3; } message NetworkCounter { string network_name = 1; PerProtocolCounters metrics = 2; } message PerProtocolCounters { uint64 in_bytes = 1; uint64 out_bytes = 2; uint64 in_packets = 3; uint64 out_packets = 4; uint64 in_flows = 5; uint64 out_flows = 6;; // Per protocol uint64 fragmented_in_packets = 7; uint64 fragmented_out_packets = 8; uint64 fragmented_in_bytes = 9; uint64 fragmented_out_bytes = 10; uint64 dropped_in_packets = 11; uint64 dropped_out_packets = 12; uint64 dropped_in_bytes = 13; uint64 dropped_out_bytes = 14; uint64 tcp_in_packets = 15; uint64 tcp_out_packets = 16; uint64 tcp_in_bytes = 17; uint64 tcp_out_bytes = 18; uint64 tcp_syn_in_packets = 19; uint64 tcp_syn_out_packets = 20; uint64 tcp_syn_in_bytes = 21; uint64 tcp_syn_out_bytes = 22; uint64 udp_in_packets = 23; uint64 udp_out_packets = 24; uint64 udp_in_bytes = 25; uint64 udp_out_bytes = 26;; uint64 icmp_in_packets = 27; uint64 icmp_out_packets = 28; uint64 icmp_in_bytes = 29; uint64 icmp_out_bytes = 30; } message HostCounter { string host_name = 1; PerProtocolCounters metrics = 2; } message GetTotalTrafficCountersRequest { bool get_per_protocol_metrics = 1; string unit = 2; } message SixtyFourNamedCounter { string counter_name = 1; uint64 counter_value = 2; // mbits, flows, packets string counter_unit = 3; string counter_description = 4; } message SystemCounter { string counter_name = 1; // counter, gauge, double_gauge string counter_type = 2; // Our counters can be integer or double uint64 counter_value = 3; // We use this field only for type double_gauge double counter_value_double = 4; // mbits, flows, packets string counter_unit = 5; string counter_description = 6; } message DisableMitigationRequest { string mitigation_uuid = 1; } message DisableMitigationReply { } // We could not create RPC method without params message BanListRequest { } message BanListReply { string ip_address = 1; string announce_uuid = 2; } message BanListHostgroupRequest { } message BanListHostgroupReply { string hostgroup_name = 1; string announce_uuid = 2; } message ExecuteBanRequest { string ip_address = 1; } message ExecuteBanReply { bool result = 1; } fastnetmon-1.2.8/src/fastnetmon_logic.cpp000066400000000000000000004310651472727706000205530ustar00rootroot00000000000000#include "fastnetmon_logic.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "all_logcpp_libraries.hpp" #include "bgp_protocol.hpp" #include "fast_library.hpp" #include "fast_platform.hpp" #include "bgp_protocol_flow_spec.hpp" #include "filter.hpp" #include "fast_endianless.hpp" // Plugins #include "netflow_plugin/netflow_collector.hpp" #ifdef ENABLE_PCAP #include "pcap_plugin/pcap_collector.hpp" #endif #include "sflow_plugin/sflow_collector.hpp" #ifdef NETMAP_PLUGIN #include "netmap_plugin/netmap_collector.hpp" #endif #ifdef FASTNETMON_ENABLE_AFPACKET #include "afpacket_plugin/afpacket_collector.hpp" #endif #ifdef ENABLE_GOBGP #include "actions/gobgp_action.hpp" #endif #include "actions/exabgp_action.hpp" // Traffic output formats #include "traffic_output_formats/protobuf/protobuf_traffic_format.hpp" #include "traffic_output_formats/protobuf/traffic_data.pb.h" // Yes, maybe it's not an good idea but with this we can guarantee working code in example plugin #include "example_plugin/example_collector.hpp" #ifdef MONGO #include #include #endif #include "fastnetmon_networks.hpp" #include "abstract_subnet_counters.hpp" #include "packet_bucket.hpp" #include "ban_list.hpp" #ifdef KAFKA #include #endif #include "fastnetmon_configuration_scheme.hpp" extern fastnetmon_configuration_t fastnetmon_global_configuration; extern uint64_t influxdb_writes_total; extern uint64_t influxdb_writes_failed; extern packet_buckets_storage_t packet_buckets_ipv6_storage; extern std::string cli_stats_file_path; extern unsigned int total_number_of_hosts_in_our_networks; extern abstract_subnet_counters_t ipv4_network_counters; extern unsigned int recalculate_speed_timeout; extern bool DEBUG_DUMP_ALL_PACKETS; extern bool DEBUG_DUMP_OTHER_PACKETS; extern uint64_t total_ipv4_packets; extern uint64_t total_ipv6_packets; extern double average_calculation_amount; extern bool print_configuration_params_on_the_screen; extern uint64_t our_ipv6_packets; extern uint64_t unknown_ip_version_packets; extern uint64_t total_simple_packets_processed; extern unsigned int maximum_time_since_bucket_start_to_remove; extern unsigned int max_ips_in_list; extern struct timeval speed_calculation_time; extern double drawing_thread_execution_time; extern std::chrono::steady_clock::time_point last_call_of_traffic_recalculation; extern std::string cli_stats_ipv6_file_path; extern unsigned int check_for_availible_for_processing_packets_buckets; extern abstract_subnet_counters_t ipv6_host_counters; extern abstract_subnet_counters_t ipv6_network_counters; extern bool process_incoming_traffic; extern bool process_outgoing_traffic; extern uint64_t total_unparsed_packets; extern time_t current_inaccurate_time; extern uint64_t total_unparsed_packets_speed; extern bool enable_connection_tracking; extern bool enable_data_collection_from_mirror; extern bool enable_netmap_collection; extern bool enable_pcap_collection; extern uint64_t incoming_total_flows_speed; extern uint64_t outgoing_total_flows_speed; extern total_speed_counters_t total_counters_ipv4; extern total_speed_counters_t total_counters_ipv6; extern total_speed_counters_t total_counters; extern host_group_ban_settings_map_t host_group_ban_settings_map; extern bool exabgp_announce_whole_subnet; extern subnet_to_host_group_map_t subnet_to_host_groups; extern bool collect_attack_pcap_dumps; extern std::mutex flow_counter_mutex; #ifdef REDIS extern unsigned int redis_port; extern std::string redis_host; extern std::string redis_prefix; extern bool redis_enabled; #endif extern int64_t netflow_ipfix_all_protocols_total_flows_speed; extern int64_t sflow_raw_packet_headers_total_speed; extern uint64_t netflow_ipfix_all_protocols_total_flows; extern uint64_t sflow_raw_packet_headers_total; #ifdef MONGO extern std::string mongodb_host; extern unsigned int mongodb_port; extern bool mongodb_enabled; extern std::string mongodb_database_name; #endif extern unsigned int number_of_packets_for_pcap_attack_dump; extern patricia_tree_t *lookup_tree_ipv4, *whitelist_tree_ipv4; extern patricia_tree_t *lookup_tree_ipv6, *whitelist_tree_ipv6; extern ban_settings_t global_ban_settings; extern bool exabgp_enabled; extern int global_ban_time; extern bool notify_script_enabled; extern std::map ban_list; extern int unban_iteration_sleep_time; extern bool unban_enabled; extern bool unban_only_if_attack_finished; extern configuration_map_t configuration_map; extern log4cpp::Category& logger; extern bool graphite_enabled; extern std::string graphite_host; extern unsigned short int graphite_port; extern std::string sort_parameter; extern std::string graphite_prefix; extern unsigned int ban_details_records_count; extern FastnetmonPlatformConfigurtion fastnetmon_platform_configuration; #include "api.hpp" #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; } unsigned int detect_attack_protocol(subnet_counter_t& speed_element, direction_t 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); } } std::string print_flow_tracking_for_ip(conntrack_main_struct_t& 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_ipv4_load() { std::stringstream buffer; attack_detection_threshold_type_t sorter_type; if (sort_parameter == "packets") { sorter_type = attack_detection_threshold_type_t::packets_per_second; } else if (sort_parameter == "bytes") { sorter_type = attack_detection_threshold_type_t::bytes_per_second; } else if (sort_parameter == "flows") { sorter_type = attack_detection_threshold_type_t::flows_per_second; } else { logger << log4cpp::Priority::INFO << "Unexpected sorter type: " << sort_parameter; sorter_type = attack_detection_threshold_type_t::packets_per_second; } std::vector> vector_for_sort; ipv4_network_counters.get_sorted_average_speed(vector_for_sort, sorter_type, attack_detection_direction_type_t::incoming); for (auto itr = vector_for_sort.begin(); itr != vector_for_sort.end(); ++itr) { subnet_counter_t* speed = &itr->second; std::string subnet_as_string = convert_subnet_to_string(itr->first); buffer << std::setw(18) << std::left << subnet_as_string; buffer << " " << "pps in: " << std::setw(8) << speed->total.in_packets << " out: " << std::setw(8) << speed->total.out_packets << " mbps in: " << std::setw(5) << convert_speed_to_mbps(speed->total.in_bytes) << " out: " << std::setw(5) << convert_speed_to_mbps(speed->total.out_bytes) << "\n"; } 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"; } if (current_ban_settings.enable_ban_ipv6) { output_buffer << "We call ban script for IPv6: yes\n"; } else { output_buffer << "We call ban script for IPv6: 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(const std::string& details, const std::string& client_ip_as_string, const attack_details_t& current_attack) { std::ofstream my_attack_details_file; // TODO: it may not work well with systems which do not allow ":" as part of file name (macOS) std::string ban_timestamp_as_string = print_time_t_in_fastnetmon_format(current_attack.ban_timestamp); std::string attack_dump_path = fastnetmon_platform_configuration.attack_details_folder + "/" + client_ip_as_string + "_" + ban_timestamp_as_string + ".txt"; 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" << attack_dump_path; } } logging_configuration_t read_logging_settings(configuration_map_t configuration_map) { logging_configuration_t logging_configuration_temp; if (configuration_map.count("logging_level") != 0) { logging_configuration_temp.logging_level = configuration_map["logging_level"]; } 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 correctly"; } 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 + "enable_ban_ipv6") != 0) { ban_settings.enable_ban_ipv6 = configuration_map[prefix + "enable_ban_ipv6"] == "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 entity bool we_should_ban_this_entity(const subnet_counter_t& average_speed_element, const ban_settings_t& current_ban_settings, attack_detection_threshold_type_t& attack_detection_source, attack_detection_direction_type_t& attack_detection_direction) { attack_detection_source = attack_detection_threshold_type_t::unknown; attack_detection_direction = attack_detection_direction_type_t::unknown; // we detect overspeed by packets if (current_ban_settings.enable_ban_for_pps && exceed_pps_speed(average_speed_element.total.in_packets, average_speed_element.total.out_packets, current_ban_settings.ban_threshold_pps)) { attack_detection_source = attack_detection_threshold_type_t::packets_per_second; return true; } if (current_ban_settings.enable_ban_for_bandwidth && exceed_mbps_speed(average_speed_element.total.in_bytes, average_speed_element.total.out_bytes, current_ban_settings.ban_threshold_mbps)) { attack_detection_source = attack_detection_threshold_type_t::bytes_per_second; 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)) { attack_detection_source = attack_detection_threshold_type_t::flows_per_second; 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)) { attack_detection_source = attack_detection_threshold_type_t::tcp_packets_per_second; 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)) { attack_detection_source = attack_detection_threshold_type_t::udp_packets_per_second; 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)) { attack_detection_source = attack_detection_threshold_type_t::icmp_packets_per_second; 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)) { attack_detection_source = attack_detection_threshold_type_t::tcp_bytes_per_second; ; 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)) { attack_detection_source = attack_detection_threshold_type_t::udp_bytes_per_second; 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)) { attack_detection_source = attack_detection_threshold_type_t::icmp_bytes_per_second; return true; } return false; } 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"; } } std::string print_flow_tracking_for_specified_protocol(contrack_map_type& protocol_map, std::string client_ip, direction_t 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_t 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(); } void convert_integer_to_conntrack_hash_struct(const uint64_t& packed_connection_data, packed_conntrack_hash_t& unpacked_data) { // Normally this code will trigger // warning: ‘void* memcpy(void*, const void*, size_t)’ copying an object of non-trivial type ‘class // packed_conntrack_hash_t’ from an array of ‘const uint64_t’ {aka ‘const long unsigned int’} [-Wclass-memaccess] // Yes, it's very bad practice to overwrite struct memory that way but we have enough safe guards (such as // explicitly packed structure and static_assert with sizeof check for structure size) in place to do it We apply // void* for target argument to suppress this warning memcpy((void*)&unpacked_data, &packed_connection_data, sizeof(uint64_t)); } // This function returns true when attack for particular IPv6 or IPv4 address is finished template requires std::is_same_v || std::is_same_v bool attack_is_finished(const T& current_subnet, abstract_subnet_counters_t& host_counters) { std::string client_ip_as_string = convert_any_ip_to_string(current_subnet); subnet_counter_t average_speed_element; // Retrieve static counters bool result = host_counters.get_average_speed(current_subnet, average_speed_element); // I think it's fine even if we run in flexible counters mode as we must have some traffic tracked by static counters in any case if (!result) { logger << log4cpp::Priority::INFO << "Could not find traffic speed for " << client_ip_as_string << " in traffic structure. But that's fine because it may be removed by cleanup logic. It means that " "traffic is " "zero for long time and we can unban host"; return true; } // Lookup network for IP as we need it for hostgorup lookup logic subnet_cidr_mask_t customer_subnet; bool lookup_result = lookup_ip_in_integer_form_inpatricia_and_return_subnet_if_found(lookup_tree_ipv4, current_subnet, customer_subnet); if (!lookup_result) { // It's not critical, we can ignore it logger << log4cpp::Priority::WARN << "Could not get customer's network for IP " << convert_ip_as_uint_to_string(current_subnet); } std::string host_group_name; ban_settings_t current_ban_settings = get_ban_settings_for_this_subnet(customer_subnet, host_group_name); attack_detection_threshold_type_t attack_detection_source; attack_detection_direction_type_t attack_detection_direction; bool should_block_static_thresholds = we_should_ban_this_entity(average_speed_element, current_ban_settings, attack_detection_source, attack_detection_direction); if (should_block_static_thresholds) { logger << log4cpp::Priority::DEBUG << "Attack to IP " << client_ip_as_string << " is still going. We should not unblock this host"; // Well, we still see an attack, skip to next iteration return false; } return true; } // Unbans host which are ready to it void execute_unban_operation_ipv4() { extern abstract_subnet_counters_t ipv4_host_counters; extern blackhole_ban_list_t ban_list_ipv4; time_t current_time; time(¤t_time); std::vector ban_list_items_for_erase; std::map ban_list_copy; // Get whole ban list content atomically ban_list_ipv4.get_whole_banlist(ban_list_copy); for (auto itr = ban_list_copy.begin(); itr != ban_list_copy.end(); ++itr) { uint32_t client_ip = itr->first; // This IP should be banned permanently and we skip any processing if (!itr->second.unban_enabled) { continue; } // This IP banned manually and we should not unban it automatically if (itr->second.attack_detection_source == attack_detection_source_t::Manual) { continue; } double time_difference = difftime(current_time, itr->second.ban_timestamp); int current_ban_time = itr->second.ban_time; // Yes, we reached end of ban time for this customer bool we_could_unban_this_ip = time_difference > current_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); if (!attack_is_finished(client_ip, ipv4_host_counters)) { logger << log4cpp::Priority::INFO << "Skip unban operation for " << client_ip_as_string << " because attack is still active"; continue; } } // Add this IP to remove list // We will remove keys really after this loop ban_list_items_for_erase.push_back(itr->first); // Call all hooks for unban subnet_ipv6_cidr_mask_t zero_ipv6_address; // It's empty for unban std::string flow_attack_details; // These are empty too boost::circular_buffer simple_packets_buffer; boost::circular_buffer raw_packets_buffer; call_blackhole_actions_per_host(attack_action_t::unban, itr->first, zero_ipv6_address, false, itr->second, attack_detection_source_t::Automatic, flow_attack_details, simple_packets_buffer, raw_packets_buffer); } // Remove all unbanned hosts from the ban list for (auto ban_element_for_erase : ban_list_items_for_erase) { ban_list_ipv4.remove_from_blackhole(ban_element_for_erase); } } // Unbans host which are ready to it void execute_unban_operation_ipv6() { time_t current_time; time(¤t_time); extern blackhole_ban_list_t ban_list_ipv6; std::vector ban_list_items_for_erase; std::map ban_list_copy; // Get whole ban list content atomically ban_list_ipv6.get_whole_banlist(ban_list_copy); for (auto itr : ban_list_copy) { // This IP should be banned permanentely and we skip any processing if (!itr.second.unban_enabled) { continue; } // This IP banned manually and we should not unban it automatically if (itr.second.attack_detection_source == attack_detection_source_t::Manual) { continue; } double time_difference = difftime(current_time, itr.second.ban_timestamp); int current_ban_time = itr.second.ban_time; // Yes, we reached end of ban time for this customer bool we_could_unban_this_ip = time_difference > current_ban_time; // We haven't reached time for unban yet if (!we_could_unban_this_ip) { continue; } if (unban_only_if_attack_finished) { logger << log4cpp::Priority::WARN << "Sorry, we do not support unban_only_if_attack_finished for IPv6"; } // Add this IP to remove list // We will remove keys really after this loop ban_list_items_for_erase.push_back(itr.first); // Call all hooks for unban uint32_t zero_ipv4_ip_address = 0; // It's empty for unban std::string flow_attack_details; // These are empty too boost::circular_buffer simple_packets_buffer; boost::circular_buffer raw_packets_buffer; call_blackhole_actions_per_host(attack_action_t::unban, zero_ipv4_ip_address, itr.first, true, itr.second, attack_detection_source_t::Automatic, flow_attack_details, simple_packets_buffer, raw_packets_buffer); } // Remove all unbanned hosts from the ban list for (auto ban_element_for_erase : ban_list_items_for_erase) { ban_list_ipv6.remove_from_blackhole(ban_element_for_erase); } } /* 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); execute_unban_operation_ipv4(); // Unban IPv6 bans execute_unban_operation_ipv6(); } } // This code is a source of race conditions of worst kind, we had to rework it ASAP std::string print_ddos_attack_details() { extern blackhole_ban_list_t ban_list_ipv4; std::stringstream output_buffer; std::map ban_list_ipv4_copy; // Get whole ban list content atomically ban_list_ipv4.get_whole_banlist(ban_list_ipv4_copy); for (auto itr : ban_list_ipv4_copy) { uint32_t client_ip = itr.first; std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip); output_buffer << client_ip_as_string << " at " << print_time_t_in_fastnetmon_format(itr.second.ban_timestamp) << std::endl; } return output_buffer.str(); } std::string get_attack_description(uint32_t client_ip, const attack_details_t& 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"; return attack_description.str(); } // Serialises traffic counters to JSON bool serialize_traffic_counters_to_json(const subnet_counter_t& traffic_counters, nlohmann::json& json_details) { try { json_details["total_incoming_traffic"] = traffic_counters.total.in_bytes; json_details["total_incoming_traffic_bits"] = traffic_counters.total.in_bytes * 8; json_details["total_outgoing_traffic"] = traffic_counters.total.out_bytes; json_details["total_outgoing_traffic_bits"] = traffic_counters.total.out_bytes * 8; json_details["total_incoming_pps"] = traffic_counters.total.in_packets; json_details["total_outgoing_pps"] = traffic_counters.total.out_packets; json_details["total_incoming_flows"] = traffic_counters.in_flows; json_details["total_outgoing_flows"] = traffic_counters.out_flows; json_details["incoming_dropped_traffic"] = traffic_counters.dropped.in_bytes; json_details["incoming_dropped_traffic_bits"] = traffic_counters.dropped.in_bytes * 8; json_details["outgoing_dropped_traffic"] = traffic_counters.dropped.out_bytes; json_details["outgoing_dropped_traffic_bits"] = traffic_counters.dropped.out_bytes * 8; json_details["incoming_dropped_pps"] = traffic_counters.dropped.in_packets; json_details["outgoing_dropped_pps"] = traffic_counters.dropped.out_packets; json_details["incoming_ip_fragmented_traffic"] = traffic_counters.fragmented.in_bytes; json_details["incoming_ip_fragmented_traffic_bits"] = traffic_counters.fragmented.in_bytes * 8; json_details["outgoing_ip_fragmented_traffic"] = traffic_counters.fragmented.out_bytes; json_details["outgoing_ip_fragmented_traffic_bits"] = traffic_counters.fragmented.out_bytes * 8; json_details["incoming_ip_fragmented_pps"] = traffic_counters.fragmented.in_packets; json_details["outgoing_ip_fragmented_pps"] = traffic_counters.fragmented.out_packets; json_details["incoming_tcp_traffic"] = traffic_counters.tcp.in_bytes; json_details["incoming_tcp_traffic_bits"] = traffic_counters.tcp.in_bytes * 8; json_details["outgoing_tcp_traffic"] = traffic_counters.tcp.out_bytes; json_details["outgoing_tcp_traffic_bits"] = traffic_counters.tcp.out_bytes * 8; json_details["incoming_tcp_pps"] = traffic_counters.tcp.in_packets; json_details["outgoing_tcp_pps"] = traffic_counters.tcp.out_packets; json_details["incoming_syn_tcp_traffic"] = traffic_counters.tcp_syn.in_bytes; json_details["incoming_syn_tcp_traffic_bits"] = traffic_counters.tcp_syn.in_bytes * 8; json_details["outgoing_syn_tcp_traffic"] = traffic_counters.tcp_syn.out_bytes; json_details["outgoing_syn_tcp_traffic_bits"] = traffic_counters.tcp_syn.out_bytes * 8; json_details["incoming_syn_tcp_pps"] = traffic_counters.tcp_syn.in_packets; json_details["outgoing_syn_tcp_pps"] = traffic_counters.tcp_syn.out_packets; json_details["incoming_udp_traffic"] = traffic_counters.udp.in_bytes; json_details["incoming_udp_traffic_bits"] = traffic_counters.udp.in_bytes * 8; json_details["outgoing_udp_traffic"] = traffic_counters.udp.out_bytes; json_details["outgoing_udp_traffic_bits"] = traffic_counters.udp.out_bytes * 8; json_details["incoming_udp_pps"] = traffic_counters.udp.in_packets; json_details["outgoing_udp_pps"] = traffic_counters.udp.out_packets; json_details["incoming_icmp_traffic"] = traffic_counters.icmp.in_bytes; json_details["incoming_icmp_traffic_bits"] = traffic_counters.icmp.in_bytes * 8; json_details["outgoing_icmp_traffic"] = traffic_counters.icmp.out_bytes; json_details["outgoing_icmp_traffic_bits"] = traffic_counters.icmp.out_bytes * 8; json_details["incoming_icmp_pps"] = traffic_counters.icmp.in_packets; json_details["outgoing_icmp_pps"] = traffic_counters.icmp.out_packets; } catch (...) { logger << log4cpp::Priority::ERROR << "Exception was triggered in attack details JSON encoder"; return false; } return true; } bool serialize_attack_description_to_json(const attack_details_t& current_attack, nlohmann::json& json_details) { // We need to catch exceptions as code may raise them here try { json_details["attack_uuid"] = current_attack.get_attack_uuid_as_string(); json_details["host_group"] = current_attack.host_group; json_details["protocol_version"] = current_attack.get_protocol_name(); } catch (...) { logger << log4cpp::Priority::ERROR << "Exception was triggered in attack details JSON encoder"; return false; } if (!serialize_traffic_counters_to_json(current_attack.traffic_counters, json_details)) { logger << log4cpp::Priority::ERROR << "Cannot add traffic counters to JSON document"; return false; } return true; } std::string get_attack_description_in_json_for_web_hooks(uint32_t client_ip, const subnet_ipv6_cidr_mask_t& client_ipv6, bool ipv6, const std::string& action_type, const attack_details_t& current_attack, const boost::circular_buffer& simple_packets_buffer) { nlohmann::json callback_info; callback_info["alert_scope"] = "host"; if (ipv6) { callback_info["ip"] = print_ipv6_address(client_ipv6.subnet_address); } else { callback_info["ip"] = convert_ip_as_uint_to_string(client_ip); } callback_info["action"] = action_type; nlohmann::json attack_details; bool attack_details_result = serialize_attack_description_to_json(current_attack, attack_details); if (attack_details_result) { callback_info["attack_details"] = attack_details; } else { logger << log4cpp::Priority::ERROR << "Cannot generate attack details for get_attack_description_in_json_for_web_hooks"; } // We add these sections only if we have anything in packet dump if (simple_packets_buffer.size() != 0) { // Detailed per field packet dump nlohmann::json packet_dump_per_field; if (write_simple_packet_as_separate_fields_dump_to_json(simple_packets_buffer, packet_dump_per_field)) { callback_info["packet_dump_detailed"] = packet_dump_per_field; } else { logger << log4cpp::Priority::ERROR << "Cannot generate detailed packet dump"; } } std::string json_as_text = callback_info.dump(); return json_as_text; } uint64_t convert_conntrack_hash_struct_to_integer(const packed_conntrack_hash_t& struct_value) { uint64_t unpacked_data = 0; memcpy(&unpacked_data, &struct_value, sizeof(uint64_t)); return unpacked_data; } /* 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_t& 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_t 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; } // exec command and pass data to it stdin // 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 programme " << cmd << " error code: " << errno << " error text: " << strerror(errno); return false; } int fputs_ret = fputs(params.c_str(), pipe); if (fputs_ret) { int pclose_return = pclose(pipe); if (pclose_return < 0) { logger << log4cpp::Priority::ERROR << "Cannot collect return status of subprocess with error: " << errno << strerror(errno); } else { logger << log4cpp::Priority::INFO << "Subprocess exit code: " << pclose_return; } return true; } else { logger << log4cpp::Priority::ERROR << "Can't pass data to stdin of programme " << cmd; pclose(pipe); return false; } return true; } // Get ban settings for this subnet or return global ban settings ban_settings_t get_ban_settings_for_this_subnet(const subnet_cidr_mask_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 auto 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; } #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); } 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 void call_blackhole_actions_per_host(attack_action_t attack_action, uint32_t client_ip, const subnet_ipv6_cidr_mask_t& client_ipv6, bool ipv6, const attack_details_t& current_attack, attack_detection_source_t attack_detection_source, const std::string& flow_attack_details, const boost::circular_buffer& simple_packets_buffer, const boost::circular_buffer& raw_packets_buffer) { extern bool usage_stats; bool ipv4 = !ipv6; std::string client_ip_as_string = ""; if (ipv4) { client_ip_as_string = convert_ip_as_uint_to_string(client_ip); } else { client_ip_as_string = print_ipv6_address(client_ipv6.subnet_address); } std::string action_name; if (attack_action == attack_action_t::ban) { action_name = "ban"; } else if (attack_action == attack_action_t::unban) { action_name = "unban"; } std::string simple_packets_dump; print_simple_packet_buffer_to_string(simple_packets_buffer, simple_packets_dump); std::string basic_attack_information_in_json = get_attack_description_in_json_for_web_hooks(client_ip, subnet_ipv6_cidr_mask_t{}, false, action_name, current_attack, simple_packets_buffer); bool store_attack_details_to_file = true; if (store_attack_details_to_file && attack_action == attack_action_t::ban) { std::string basic_attack_information = get_attack_description(client_ip, current_attack); std::string full_attack_description = basic_attack_information + "\n\nAttack traffic dump\n\n" + simple_packets_dump + "\n\nFlow dump\n\n" + flow_attack_details; if (store_attack_details_to_file) { print_attack_details_to_file(full_attack_description, client_ip_as_string, current_attack); } } if (notify_script_enabled) { 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); if (attack_action == attack_action_t::ban) { std::string basic_attack_information = get_attack_description(client_ip, current_attack); std::string full_attack_description = basic_attack_information + "\n\nAttack traffic dump\n\n" + simple_packets_dump + "\n\nFlow dump\n\n" + flow_attack_details; std::string script_call_params = fastnetmon_platform_configuration.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 destructive // We will pass attack details over stdin boost::thread exec_thread(exec_with_stdin_params, script_call_params, full_attack_description); exec_thread.detach(); logger << log4cpp::Priority::INFO << "Script for ban client is finished: " << client_ip_as_string; } else if (attack_action == attack_action_t::unban) { std::string script_call_params = fastnetmon_platform_configuration.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_no_error_check, script_call_params); exec_thread.detach(); logger << log4cpp::Priority::INFO << "Script for unban client is finished: " << client_ip_as_string; } } if (exabgp_enabled && ipv4) { logger << log4cpp::Priority::INFO << "Call ExaBGP for " << action_name << " client started: " << client_ip_as_string; boost::thread exabgp_thread(exabgp_ban_manage, action_name, client_ip_as_string, current_attack.customer_network); exabgp_thread.detach(); logger << log4cpp::Priority::INFO << "Call to ExaBGP for " << action_name << "client is finished: " << client_ip_as_string; } #ifdef ENABLE_GOBGP if (fastnetmon_global_configuration.gobgp) { logger << log4cpp::Priority::INFO << "Call GoBGP for " << action_name << " client started: " << client_ip_as_string; boost::thread gobgp_thread(gobgp_ban_manage, action_name, ipv6, client_ip, client_ipv6, current_attack); gobgp_thread.detach(); logger << log4cpp::Priority::INFO << "Call to GoBGP for " << action_name << " client is finished: " << client_ip_as_string; } #endif if (attack_action == attack_action_t::ban) { #ifdef REDIS if (redis_enabled && ipv4) { 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; // If we have flow dump put in redis too if (!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 } if (attack_action == attack_action_t::ban) { #ifdef MONGO if (mongodb_enabled && ipv4) { 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 (usage_stats) { boost::thread attack_report_thread(send_attack_data_to_reporting_server, basic_attack_information_in_json); attack_report_thread.detach(); } } #ifdef MONGO void store_data_in_mongo(std::string key_name, std::string attack_details_json) { mongoc_client_t* client; mongoc_collection_t* collection; 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 // pretty print channel speed in pps and MBit std::string print_channel_speed(std::string traffic_type, direction_t packet_direction) { uint64_t speed_in_pps = total_counters_ipv4.total_speed_average_counters[packet_direction].total.packets; uint64_t speed_in_bps = total_counters_ipv4.total_speed_average_counters[packet_direction].total.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"; } } return stream.str(); } void traffic_draw_ipv6_program() { std::stringstream output_buffer; // logger< diff = std::chrono::steady_clock::now() - start_time; drawing_thread_execution_time = diff.count(); } std::string get_human_readable_threshold_type(attack_detection_threshold_type_t detecttion_type) { if (detecttion_type == attack_detection_threshold_type_t::unknown) { return "unknown"; } else if (detecttion_type == attack_detection_threshold_type_t::packets_per_second) { return "packets per second"; } else if (detecttion_type == attack_detection_threshold_type_t::bytes_per_second) { return "bytes per second"; } else if (detecttion_type == attack_detection_threshold_type_t::flows_per_second) { return "flows per second"; } else if (detecttion_type == attack_detection_threshold_type_t::tcp_packets_per_second) { return "tcp packets per second"; } else if (detecttion_type == attack_detection_threshold_type_t::tcp_syn_packets_per_second) { return "tcp syn packets per second"; } else if (detecttion_type == attack_detection_threshold_type_t::tcp_syn_bytes_per_second) { return "tcp syn bytes per second"; } else if (detecttion_type == attack_detection_threshold_type_t::udp_packets_per_second) { return "udp packets per second"; } else if (detecttion_type == attack_detection_threshold_type_t::icmp_packets_per_second) { return "icmp packets per second"; } else if (detecttion_type == attack_detection_threshold_type_t::tcp_bytes_per_second) { return "tcp bytes per second"; } else if (detecttion_type == attack_detection_threshold_type_t::udp_bytes_per_second) { return "udp bytes per second"; } else if (detecttion_type == attack_detection_threshold_type_t::icmp_bytes_per_second) { return "icmp bytes per second"; } return "unknown"; } // This function fills attack information from different information sources bool fill_attack_information( attack_details_t& current_attack, std::string& host_group_name, std::string& parent_host_group_name, bool unban_enabled, int ban_time) { uint64_t pps = 0; uint64_t in_pps = current_attack.traffic_counters.total.in_packets; uint64_t out_pps = current_attack.traffic_counters.total.out_packets; uint64_t in_bps = current_attack.traffic_counters.total.in_bytes; uint64_t out_bps = current_attack.traffic_counters.total.out_bytes; direction_t data_direction; // TODO: move this logic to different function!!! // 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(current_attack.traffic_counters, data_direction); current_attack.host_group = host_group_name; current_attack.parent_host_group = parent_host_group_name; std::string data_direction_as_string = get_direction_name(data_direction); logger << log4cpp::Priority::INFO << "We run attack block code with following params" << " in: " << in_pps << " pps " << convert_speed_to_mbps(in_bps) << " mbps" << " out: " << out_pps << " pps " << convert_speed_to_mbps(out_bps) << " mbps" << " and we decided it's " << data_direction_as_string << " attack"; // Store ban time time(¤t_attack.ban_timestamp); // set ban time in seconds current_attack.ban_time = 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; return true; } // Speed recalculation function for IPv6 hosts calls it for each host during speed recalculation void speed_calculation_callback_local_ipv6(const subnet_ipv6_cidr_mask_t& current_subnet, const subnet_counter_t& current_average_speed_element) { // We should check thresholds only for per host counters for IPv6 and only when any ban actions for IPv6 traffic were enabled if (!global_ban_settings.enable_ban_ipv6) { return; } extern blackhole_ban_list_t ban_list_ipv6; // We support only global group std::string host_group_name = "global"; attack_detection_threshold_type_t attack_detection_source; attack_detection_direction_type_t attack_detection_direction; bool should_ban = we_should_ban_this_entity(current_average_speed_element, global_ban_settings, attack_detection_source, attack_detection_direction); if (!should_ban) { return; } // This code works only for /128 subnets bool in_white_list = ip_belongs_to_patricia_tree_ipv6(whitelist_tree_ipv6, current_subnet.subnet_address); if (in_white_list) { // logger << log4cpp::Priority::INFO << "This IP was whitelisted"; return; } bool we_already_have_buckets_for_this_ip = packet_buckets_ipv6_storage.we_have_bucket_for_this_ip(current_subnet); if (we_already_have_buckets_for_this_ip) { return; } bool this_ip_is_already_banned = ban_list_ipv6.is_blackholed(current_subnet); if (this_ip_is_already_banned) { return; } std::string ddos_detection_threshold_as_string = get_human_readable_threshold_type(attack_detection_source); logger << log4cpp::Priority::INFO << "We have detected IPv6 attack for " << print_ipv6_cidr_subnet(current_subnet) << " with " << ddos_detection_threshold_as_string << " threshold host group: " << host_group_name; std::string parent_group; attack_details_t attack_details; attack_details.traffic_counters = current_average_speed_element; fill_attack_information(attack_details, host_group_name, parent_group, unban_enabled, global_ban_time); attack_details.ipv6 = true; // TODO: Also, we should find IPv6 network for attack here bool enable_backet_capture = packet_buckets_ipv6_storage.enable_packet_capture(current_subnet, attack_details, collection_pattern_t::ONCE); if (!enable_backet_capture) { logger << log4cpp::Priority::ERROR << "Could not enable packet capture for deep analytics for IPv6 " << print_ipv6_cidr_subnet(current_subnet); return; } logger << log4cpp::Priority::INFO << "Enabled packet capture for IPv6 " << print_ipv6_address(current_subnet.subnet_address); } // Speed recalculation function for IPv6 networks // It's just stub, we do not execute any actions for it void speed_callback_subnet_ipv6(subnet_ipv6_cidr_mask_t* subnet, subnet_counter_t* speed_element) { return; } // This function works as callback from main speed calculation thread and decides when we should block host using static thresholds void speed_calculation_callback_local_ipv4(const uint32_t& client_ip, const subnet_counter_t& speed_element) { extern abstract_subnet_counters_t ipv4_host_counters; extern blackhole_ban_list_t ban_list_ipv4; extern packet_buckets_storage_t packet_buckets_ipv4_storage; extern patricia_tree_t* whitelist_tree_ipv4; extern patricia_tree_t* lookup_tree_ipv4; extern boost::circular_buffer ipv4_packets_circular_buffer; // Check global ban settings if (!global_ban_settings.enable_ban) { return; } // Lookup network for IP as we need it for hostgorup lookup logic subnet_cidr_mask_t customer_subnet; bool lookup_result = lookup_ip_in_integer_form_inpatricia_and_return_subnet_if_found(lookup_tree_ipv4, client_ip, customer_subnet); if (!lookup_result) { // It's not critical, we can ignore it logger << log4cpp::Priority::WARN << "Could not get customer's network for IP " << convert_ip_as_uint_to_string(client_ip); } std::string host_group_name; ban_settings_t current_ban_settings = get_ban_settings_for_this_subnet(customer_subnet, host_group_name); // Hostgroup has blocks disabled if (!current_ban_settings.enable_ban) { return; } attack_details_t attack_details; // Static thresholds attack_detection_threshold_type_t attack_detection_source; attack_detection_direction_type_t attack_detection_direction; bool should_block = we_should_ban_this_entity(speed_element, current_ban_settings, attack_detection_source, attack_detection_direction); if (!should_block) { return; } // We should execute check over whitelist // In common case, this check is pretty complicated and we should execute it only for hosts which exceed // threshold bool in_white_list = ip_belongs_to_patricia_tree(whitelist_tree_ipv4, client_ip); // And if we found host here disable any actions about blocks if (in_white_list) { return; } // If we decided to block this host we should check two cases: // 1) Already banned // 2) We already started packets collection for this IP address // They could be filled or not yet filled // TODO: with this check we should REMOVE items from bucket storage when attack handled bool we_already_have_buckets_for_this_ip = packet_buckets_ipv4_storage.we_have_bucket_for_this_ip(client_ip); if (we_already_have_buckets_for_this_ip) { return; } bool this_ip_is_already_banned = ban_list_ipv4.is_blackholed(client_ip); if (this_ip_is_already_banned) { return; } std::string ddos_detection_threshold_as_string = get_human_readable_threshold_type(attack_detection_source); std::string ddos_detection_direction = get_human_readable_attack_detection_direction(attack_detection_direction); logger << log4cpp::Priority::INFO << "We have detected attack for " << convert_ip_as_uint_to_string(client_ip) << " using " << ddos_detection_threshold_as_string << " threshold " << "in direction " << ddos_detection_direction << " " << "host group: " << host_group_name; attack_details.traffic_counters = speed_element; // Set threshold direction attack_details.attack_detection_direction = attack_detection_direction; // Set threshold type attack_details.attack_detection_threshold = attack_detection_source; // Fill attack details. This operation is pretty simple and involves only long prefix match lookup + // field copy std::string parent_group; fill_attack_information(attack_details, host_group_name, parent_group, unban_enabled, global_ban_time); attack_details.customer_network = customer_subnet; bool enable_backet_capture = packet_buckets_ipv4_storage.enable_packet_capture(client_ip, attack_details, collection_pattern_t::ONCE); if (!enable_backet_capture) { logger << log4cpp::Priority::ERROR << "Could not enable packet capture for deep analytics for IP " << convert_ip_as_uint_to_string(client_ip); return; } logger << log4cpp::Priority::INFO << "Enabled packet capture for IP " << convert_ip_as_uint_to_string(client_ip); return; } // Increments in and out flow counters // Returns false when we cannot find flow for this IP bool increment_flow_counters(subnet_counter_t& new_speed_element, uint32_t client_ip, double speed_calc_period) { extern map_of_vector_counters_for_flow_t SubnetVectorMapFlow; extern std::mutex flow_counter_mutex; std::lock_guard lock_guard(flow_counter_mutex); auto current_flow_counter = SubnetVectorMapFlow.find(client_ip); if (current_flow_counter == SubnetVectorMapFlow.end()) { // We have no entries for this IP return false; } uint64_t total_out_flows = (uint64_t)current_flow_counter->second.out_tcp.size() + (uint64_t)current_flow_counter->second.out_udp.size() + (uint64_t)current_flow_counter->second.out_icmp.size() + (uint64_t)current_flow_counter->second.out_other.size(); uint64_t total_in_flows = (uint64_t)current_flow_counter->second.in_tcp.size() + (uint64_t)current_flow_counter->second.in_udp.size() + (uint64_t)current_flow_counter->second.in_icmp.size() + (uint64_t)current_flow_counter->second.in_other.size(); // logger << log4cpp::Priority::DEBUG << "total out flows: " << total_out_flows << " total in flows: " << total_in_flows << " speed calc period: " << speed_calc_period; 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); return true; } /* Calculate speed for all connnections */ void recalculate_speed() { // logger<< log4cpp::Priority::INFO<<"We run recalculate_speed"; double speed_calc_period = recalculate_speed_timeout; extern abstract_subnet_counters_t ipv4_host_counters; extern map_of_vector_counters_for_flow_t SubnetVectorMapFlow; std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); // Calculate duration of our sleep duration as it may be altered by OS behaviour (i.e. process scheduler) // And if it differs from reference value then we need to adjust it and use new value std::chrono::duration diff = start_time - last_call_of_traffic_recalculation; double time_difference = diff.count(); // Handle case of time moving backwards if (time_difference < 0) { // It must not happen as our time source is explicitly monotonic: https://en.cppreference.com/w/cpp/chrono/steady_clock logger << log4cpp::Priority::ERROR << "Negative delay for traffic calculation " << time_difference; logger << log4cpp::Priority::ERROR << "This must not happen, please report this issue to maintainers. Skipped iteration"; return; } // logger << log4cpp::Priority::INFO << "Delay in seconds " << time_difference; // Zero or positive delay if (time_difference < recalculate_speed_timeout) { // It could occur on toolkit start or in some weird cases of Linux scheduler // I really saw cases when sleep executed in zero seconds: // [WARN] Sleep time expected: 1. Sleep time experienced: 0 // But we have handlers for such case and should not bother client about with it // And we are using DEBUG level here logger << log4cpp::Priority::DEBUG << "We skip one iteration of speed_calc because it runs so early! That's " "really impossible! Please ask support."; logger << log4cpp::Priority::DEBUG << "Sleep time expected: " << recalculate_speed_timeout << ". Sleep time experienced: " << time_difference; 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 << " seconds"; speed_calc_period = time_difference; } uint64_t incoming_total_flows = 0; uint64_t outgoing_total_flows = 0; ipv4_network_counters.recalculate_speed(speed_calc_period, (double)average_calculation_amount, nullptr); uint64_t flow_exists_for_ip = 0; uint64_t flow_does_not_exist_for_ip = 0; ipv4_host_counters.recalculate_speed(speed_calc_period, (double)average_calculation_amount, speed_calculation_callback_local_ipv4, [&outgoing_total_flows, &incoming_total_flows, &flow_exists_for_ip, &flow_does_not_exist_for_ip](const uint32_t& ip, subnet_counter_t& new_speed_element, double speed_calc_period) { if (enable_connection_tracking) { bool res = increment_flow_counters(new_speed_element, fast_ntoh(ip), speed_calc_period); if (res) { // Increment global counter outgoing_total_flows += new_speed_element.out_flows; incoming_total_flows += new_speed_element.in_flows; flow_exists_for_ip++; // logger << log4cpp::Priority::DEBUG << convert_ipv4_subnet_to_string(subnet) // << "in flows: " << new_speed_element.in_flows << " out flows: " << // new_speed_element.out_flows; } else { // We did not find record flow_does_not_exist_for_ip++; } } }); // Calculate IPv6 per network traffic ipv6_network_counters.recalculate_speed(speed_calc_period, (double)average_calculation_amount, nullptr); // Recalculate traffic for hosts ipv6_host_counters.recalculate_speed(speed_calc_period, (double)average_calculation_amount, speed_calculation_callback_local_ipv6); if (enable_connection_tracking) { // 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); zeroify_all_flow_counters(); } total_unparsed_packets_speed = uint64_t((double)total_unparsed_packets / (double)speed_calc_period); total_unparsed_packets = 0; // Calculate IPv4 total traffic speed total_counters_ipv4.calculate_speed(speed_calc_period, (double)average_calculation_amount); // Do same for IPv6 total_counters_ipv6.calculate_speed(speed_calc_period, (double)average_calculation_amount); // Calculate total IPv4 + IPv6 traffic total_counters.calculate_speed(speed_calc_period, (double)average_calculation_amount); // Set time of previous startup last_call_of_traffic_recalculation = std::chrono::steady_clock::now(); // Calculate time we spent to calculate speed in this function std::chrono::duration speed_calculation_diff = std::chrono::steady_clock::now() - start_time; // Populate fields of old structure for backward compatibility double integer = 0; // Split double into integer and fractional parts double fractional = std::modf(speed_calculation_diff.count(), &integer); speed_calculation_time.tv_sec = time_t(integer); // timeval field tv_usec has type long on Windows #ifdef _WIN32 speed_calculation_time.tv_usec = long(fractional * 1000000); #else speed_calculation_time.tv_usec = suseconds_t(fractional * 1000000); #endif // Report cases when we calculate speed too slow if (speed_calculation_time.tv_sec > 0) { logger << log4cpp::Priority::ERROR << "ALERT. Toolkit working incorrectly. We should calculate speed counters in <1 second"; logger << log4cpp::Priority::ERROR << "Traffic was calculated in: " << speed_calculation_time.tv_sec << " sec " << speed_calculation_time.tv_usec << " microseconds"; logger << log4cpp::Priority::ERROR << "Please use CPU with higher frequency and better single core performance or reduce number of monitored hosts"; } } std::string draw_table_ipv4_hash(attack_detection_direction_type_t sort_direction, attack_detection_threshold_type_t sorter_type) { extern abstract_subnet_counters_t ipv4_host_counters; extern blackhole_ban_list_t ban_list_ipv4; std::stringstream output_buffer; unsigned int shift_for_sort = max_ips_in_list; // Allocate vector with size which matches number of required elements std::vector> vector_for_sort(shift_for_sort); ipv4_host_counters.get_top_k_average_speed(vector_for_sort, sorter_type, sort_direction); for (const auto& item: vector_for_sort) { // When we do not have enough hosts in output vector we will keep all entries nil, filter out them if (item.first == 0) { continue; } uint32_t client_ip = item.first; std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip); uint64_t pps = 0; uint64_t bps = 0; uint64_t flows = 0; // Here we could have average or instantaneous speed const subnet_counter_t& current_speed_element = item.second; // Create polymorphic pps, byte and flow counters if (sort_direction == attack_detection_direction_type_t::incoming) { pps = current_speed_element.total.in_packets; bps = current_speed_element.total.in_bytes; flows = current_speed_element.in_flows; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { pps = current_speed_element.total.out_packets; bps = current_speed_element.total.out_bytes; flows = current_speed_element.out_flows; } uint64_t mbps = convert_speed_to_mbps(bps); // We use setw for alignment output_buffer << client_ip_as_string << "\t\t"; std::string is_banned = ban_list_ipv4.is_blackholed(client_ip) ? " *banned* " : ""; 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; } return output_buffer.str(); } std::string draw_table_ipv6(attack_detection_direction_type_t sort_direction, attack_detection_threshold_type_t sorter_type) { std::vector vector_for_sort; ssize_t size_of_ipv6_counters_map = 0; std::stringstream output_buffer; extern blackhole_ban_list_t ban_list_ipv6; // TODO: implement method for such tasks { std::lock_guard lock_guard(ipv6_host_counters.counter_map_mutex); size_of_ipv6_counters_map = ipv6_host_counters.average_speed_map.size(); } logger << log4cpp::Priority::DEBUG << "We create sort buffer with " << size_of_ipv6_counters_map << " elements"; vector_for_sort.reserve(size_of_ipv6_counters_map); for (const auto& metric_pair : ipv6_host_counters.average_speed_map) { vector_for_sort.push_back(metric_pair); } // If we have so small number of elements reduce list length unsigned int vector_size = vector_for_sort.size(); unsigned int shift_for_sort = max_ips_in_list; if (vector_size < shift_for_sort) { shift_for_sort = vector_size; } logger << log4cpp::Priority::DEBUG << "Start vector sort"; std::partial_sort(vector_for_sort.begin(), vector_for_sort.begin() + shift_for_sort, vector_for_sort.end(), TrafficComparatorClass(sort_direction, sorter_type)); logger << log4cpp::Priority::DEBUG << "Finished vector sort"; 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 >= shift_for_sort) { break; } element_number++; std::string client_ip_as_string; if (ii->first.cidr_prefix_length == 128) { // For host addresses we do not need prefix client_ip_as_string = print_ipv6_address(ii->first.subnet_address); } else { client_ip_as_string = print_ipv6_cidr_subnet(ii->first); } uint64_t pps = 0; uint64_t bps = 0; uint64_t flows = 0; // Here we could have average or instantaneous speed subnet_counter_t* current_speed_element = &ii->second; // Create polymorphic pps, byte and flow counters if (sort_direction == attack_detection_direction_type_t::incoming) { pps = current_speed_element->total.in_packets; bps = current_speed_element->total.in_bytes; flows = current_speed_element->in_flows; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { pps = current_speed_element->total.out_packets; bps = current_speed_element->total.out_bytes; flows = current_speed_element->out_flows; } uint64_t mbps = convert_speed_to_mbps(bps); // We use setw for alignment output_buffer << client_ip_as_string << "\t"; std::string is_banned = ban_list_ipv6.is_blackholed(ii->first) ? " *banned* " : ""; 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; } return output_buffer.str(); } void print_screen_contents_into_file(std::string screen_data_stats_param, std::string file_path) { std::ofstream screen_data_file; screen_data_file.open(file_path.c_str(), std::ios::trunc); if (screen_data_file.is_open()) { // Set 660 permissions to file for security reasons chmod(cli_stats_file_path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); screen_data_file << screen_data_stats_param; screen_data_file.close(); } else { logger << log4cpp::Priority::ERROR << "Can't print program screen into file: " << file_path; } } void zeroify_all_flow_counters() { extern map_of_vector_counters_for_flow_t SubnetVectorMapFlow; extern std::mutex flow_counter_mutex; std::lock_guard lock_guard(flow_counter_mutex); SubnetVectorMapFlow.clear(); } #ifdef KAFKA // Exports traffic to Kafka void export_to_kafka(const simple_packet_t& current_packet) { extern std::string kafka_traffic_export_topic; extern cppkafka::Producer* kafka_traffic_export_producer; extern kafka_traffic_export_format_t kafka_traffic_export_format; if (kafka_traffic_export_format == kafka_traffic_export_format_t::JSON) { nlohmann::json json_packet; if (!serialize_simple_packet_to_json(current_packet, json_packet)) { return; } std::string simple_packet_as_json_string = json_packet.dump(); try { kafka_traffic_export_producer->produce( cppkafka::MessageBuilder(kafka_traffic_export_topic).partition(RD_KAFKA_PARTITION_UA).payload(simple_packet_as_json_string)); } catch (...) { // We do not log it as it will flood log files // logger << log4cpp::Priority::ERROR << "Kafka write failed"; } } else if (kafka_traffic_export_format == kafka_traffic_export_format_t::Protobuf) { TrafficData traffic_data; // Encode Packet in protobuf write_simple_packet_to_protobuf(current_packet, traffic_data); std::string output_data; if (!traffic_data.SerializeToString(&output_data)) { // Encoding error happened return; } try { kafka_traffic_export_producer->produce( cppkafka::MessageBuilder(kafka_traffic_export_topic).partition(RD_KAFKA_PARTITION_UA).payload(output_data)); } catch (...) { // We do not log it as it will flood log files // logger << log4cpp::Priority::ERROR << "Kafka write failed"; } } else { // Unknown format return; } try { kafka_traffic_export_producer->flush(); } catch (...) { // We do not log it as it will flood log files // logger << log4cpp::Priority::ERROR << "Kafka flush failed"; } } #endif // Adds traffic to buckets from hot path template void collect_traffic_to_buckets_ipv6(const simple_packet_t& current_packet, packet_buckets_storage_t& packet_buckets_storage) { // Yes, it's not very optimal to construct subnet_ipv6_cidr_mask_t again but it offers way clearer logic // In future we should get rid of subnet_ipv6_cidr_mask_t and use subnet_address directly // if (current_packet.packet_direction == OUTGOING) { subnet_ipv6_cidr_mask_t ipv6_address; ipv6_address.set_cidr_prefix_length(128); ipv6_address.set_subnet_address(¤t_packet.src_ipv6); packet_buckets_storage.add_packet_to_storage(ipv6_address, current_packet); } else if (current_packet.packet_direction == INCOMING) { subnet_ipv6_cidr_mask_t ipv6_address; ipv6_address.set_cidr_prefix_length(128); ipv6_address.set_subnet_address(¤t_packet.dst_ipv6); packet_buckets_storage.add_packet_to_storage(ipv6_address, current_packet); } } // Process IPv6 traffic void process_ipv6_packet(simple_packet_t& current_packet) { 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; #ifdef KAFKA extern bool kafka_traffic_export; #endif subnet_ipv6_cidr_mask_t ipv6_cidr_subnet; current_packet.packet_direction = get_packet_direction_ipv6(lookup_tree_ipv6, current_packet.src_ipv6, current_packet.dst_ipv6, ipv6_cidr_subnet); #ifdef KAFKA if (kafka_traffic_export) { export_to_kafka(current_packet); } #endif // Skip processing of specific traffic direction if ((current_packet.packet_direction == INCOMING && !process_incoming_traffic) or (current_packet.packet_direction == OUTGOING && !process_outgoing_traffic)) { return; } #ifdef USE_NEW_ATOMIC_BUILTINS __atomic_add_fetch(&total_counters_ipv6.total_counters[current_packet.packet_direction].packets, sampled_number_of_packets, __ATOMIC_RELAXED); __atomic_add_fetch(&total_counters_ipv6.total_counters[current_packet.packet_direction].bytes, sampled_number_of_bytes, __ATOMIC_RELAXED); __atomic_add_fetch(&total_ipv6_packets, 1, __ATOMIC_RELAXED); #else __sync_fetch_and_add(&total_counters_ipv6.total_counters[current_packet.packet_direction].total.packets, sampled_number_of_packets); __sync_fetch_and_add(&total_counters_ipv6.total_counters[current_packet.packet_direction].total.bytes, sampled_number_of_bytes); __sync_fetch_and_add(&total_ipv6_packets, 1); #endif { std::lock_guard lock_guard(ipv6_network_counters.counter_map_mutex); // We will create keys for new subnet here on demand subnet_counter_t* counter_ptr = &ipv6_network_counters.counter_map[ipv6_cidr_subnet]; if (current_packet.packet_direction == OUTGOING) { counter_ptr->total.out_packets += sampled_number_of_packets; counter_ptr->total.out_bytes += sampled_number_of_bytes; } else if (current_packet.packet_direction == INCOMING) { counter_ptr->total.in_packets += sampled_number_of_packets; counter_ptr->total.in_bytes += sampled_number_of_bytes; } } // Here I use counters allocated per /128. In some future we could offer option to count them in diffenrent way // (/64, /96) { if (current_packet.packet_direction == OUTGOING) { subnet_ipv6_cidr_mask_t ipv6_address; ipv6_address.set_cidr_prefix_length(128); ipv6_address.set_subnet_address(¤t_packet.src_ipv6); ipv6_host_counters.increment_outgoing_counters_for_key(ipv6_address, current_packet, sampled_number_of_packets, sampled_number_of_bytes); } else if (current_packet.packet_direction == INCOMING) { subnet_ipv6_cidr_mask_t ipv6_address; ipv6_address.set_cidr_prefix_length(128); ipv6_address.set_subnet_address(¤t_packet.dst_ipv6); ipv6_host_counters.increment_incoming_counters_for_key(ipv6_address, current_packet, sampled_number_of_packets, sampled_number_of_bytes); } // Collect packets for DDoS analytics engine collect_traffic_to_buckets_ipv6(current_packet, packet_buckets_ipv6_storage); } return; } // Adds traffic to buckets from hot path template void collect_traffic_to_buckets_ipv4(const simple_packet_t& current_packet, packet_buckets_storage_t& packet_buckets_storage) { if (current_packet.packet_direction == OUTGOING) { // With this code we will add parsed packets and their raw versions (if we have they) to circular buffer to // we are interested about they packet_buckets_storage.add_packet_to_storage(current_packet.src_ip, current_packet); } else if (current_packet.packet_direction == INCOMING) { // With this code we will add parsed packets and their raw versions (if we have they) to circular buffer to // we are interested about they packet_buckets_storage.add_packet_to_storage(current_packet.dst_ip, current_packet); } } // Process simple unified packet void process_packet(simple_packet_t& current_packet) { extern abstract_subnet_counters_t ipv4_host_counters; extern packet_buckets_storage_t packet_buckets_ipv4_storage; extern std::vector static_flowspec_based_whitelist; extern map_of_vector_counters_for_flow_t SubnetVectorMapFlow; extern uint64_t total_flowspec_whitelist_packets; #ifdef KAFKA extern bool kafka_traffic_export; #endif // Packets dump is very useful for bug hunting if (DEBUG_DUMP_ALL_PACKETS) { logger << log4cpp::Priority::INFO << "Dump: " << print_simple_packet(current_packet); } // Increment counter about total number of packets processes here #ifdef USE_NEW_ATOMIC_BUILTINS __atomic_add_fetch(&total_simple_packets_processed, 1, __ATOMIC_RELAXED); if (current_packet.ip_protocol_version == 4) { __atomic_add_fetch(&total_ipv4_packets, 1, __ATOMIC_RELAXED); } else if (current_packet.ip_protocol_version == 6) { __atomic_add_fetch(&total_ipv6_packets, 1, __ATOMIC_RELAXED); } else { // Non IPv4 and non IPv6 packets __atomic_add_fetch(&unknown_ip_version_packets, 1, __ATOMIC_RELAXED); return; } #else __sync_fetch_and_add(&total_simple_packets_processed, 1); if (current_packet.ip_protocol_version == 4) { __sync_fetch_and_add(&total_ipv4_packets, 1); } else if (current_packet.ip_protocol_version == 6) { __sync_fetch_and_add(&total_ipv6_packets, 1); } else { // Non IPv4 and non IPv6 packets __atomic_add_fetch(&unknown_ip_version_packets, 1, __ATOMIC_RELAXED); return; } #endif // Process IPv6 traffic in differnt function if (current_packet.ip_protocol_version == 6) { return process_ipv6_packet(current_packet); } 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; if (current_packet.ip_protocol_version != 4) { return; } // Subnet for found IPs subnet_cidr_mask_t current_subnet; current_packet.packet_direction = get_packet_direction(lookup_tree_ipv4, current_packet.src_ip, current_packet.dst_ip, current_subnet); #ifdef KAFKA if (kafka_traffic_export) { export_to_kafka(current_packet); } #endif // Check against static flow spec based whitelist if ((current_packet.packet_direction == INCOMING or current_packet.packet_direction == OUTGOING) && static_flowspec_based_whitelist.size() > 0) { // We do not use mutex here because we load this list only on startup bool should_we_whitelist_this_packet = filter_packet_by_flowspec_rule_list(current_packet, static_flowspec_based_whitelist); if (should_we_whitelist_this_packet) { total_flowspec_whitelist_packets++; return; } } // It's useful in case when we can't find what packets do not processed correctly if (DEBUG_DUMP_OTHER_PACKETS && current_packet.packet_direction == OTHER) { logger << log4cpp::Priority::INFO << "Dump other: " << print_simple_packet(current_packet); } // Skip processing of specific traffic direction if ((current_packet.packet_direction == INCOMING && !process_incoming_traffic) or (current_packet.packet_direction == OUTGOING && !process_outgoing_traffic)) { return; } if (current_packet.packet_direction == OUTGOING or current_packet.packet_direction == INCOMING) { std::lock_guard lock_guard(ipv4_network_counters.counter_map_mutex); // We will create keys for new subnet here on demand subnet_counter_t& counters = ipv4_network_counters.counter_map[current_subnet]; if (current_packet.packet_direction == OUTGOING) { increment_outgoing_counters(counters, current_packet, sampled_number_of_packets, sampled_number_of_bytes); } else if (current_packet.packet_direction == INCOMING) { increment_incoming_counters(counters, current_packet, sampled_number_of_packets, sampled_number_of_bytes); } } /* 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 */ #ifdef USE_NEW_ATOMIC_BUILTINS __atomic_add_fetch(&total_counters_ipv4.total_counters[current_packet.packet_direction].packets, sampled_number_of_packets, __ATOMIC_RELAXED); __atomic_add_fetch(&total_counters_ipv4.total_counters[current_packet.packet_direction].bytes, sampled_number_of_bytes, __ATOMIC_RELAXED); #else __sync_fetch_and_add(&total_counters_ipv4.total_counters[current_packet.packet_direction].total.packets, sampled_number_of_packets); __sync_fetch_and_add(&total_counters_ipv4.total_counters[current_packet.packet_direction].total.bytes, sampled_number_of_bytes); #endif // Add traffic to buckets when we have them collect_traffic_to_buckets_ipv4(current_packet, packet_buckets_ipv4_storage); // Increment counters for all local hosts using new counters if (current_packet.packet_direction == OUTGOING) { ipv4_host_counters.increment_outgoing_counters_for_key(current_packet.src_ip, current_packet, sampled_number_of_packets, sampled_number_of_bytes); } else if (current_packet.packet_direction == INCOMING) { ipv4_host_counters.increment_incoming_counters_for_key(current_packet.dst_ip, current_packet, sampled_number_of_packets, sampled_number_of_bytes); } else { // No reasons to keep locks for other or internal } // Increment main and per protocol packet counters if (current_packet.packet_direction == OUTGOING) { if (enable_connection_tracking) { increment_outgoing_flow_counters(fast_ntoh(current_packet.src_ip), current_packet, sampled_number_of_packets, sampled_number_of_bytes); } } else if (current_packet.packet_direction == INCOMING) { if (enable_connection_tracking) { increment_incoming_flow_counters(fast_ntoh(current_packet.dst_ip), current_packet, sampled_number_of_packets, sampled_number_of_bytes); } } else if (current_packet.packet_direction == INTERNAL) { } } void system_counters_speed_thread_handler() { while (true) { auto netflow_ipfix_all_protocols_total_flows_previous = netflow_ipfix_all_protocols_total_flows; auto sflow_raw_packet_headers_total_previous = sflow_raw_packet_headers_total; // We recalculate it each second to avoid confusion boost::this_thread::sleep(boost::posix_time::seconds(1)); netflow_ipfix_all_protocols_total_flows_speed = int64_t((float)netflow_ipfix_all_protocols_total_flows - (float)netflow_ipfix_all_protocols_total_flows_previous); sflow_raw_packet_headers_total_speed = int64_t((float)sflow_raw_packet_headers_total - (float)sflow_raw_packet_headers_total_previous); } } // Generates inaccurate time for fast time operations void inaccurate_time_generator() { extern time_t current_inaccurate_time; while (true) { boost::this_thread::sleep(boost::posix_time::seconds(1)); // We use this thread to update time each second time_t current_time = 0; time(¤t_time); // Update global time, yes, it may became inaccurate due to thread sync but that's OK for our purposes current_inaccurate_time = current_time; } } // Creates compressed flow tracking structure void init_incoming_flow_counting_structure(packed_conntrack_hash_t& flow_tracking_structure, const simple_packet_t& current_packet) { 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; } // client_ip is expected in host byte order // client_ip in host byte order! void increment_incoming_flow_counters(uint32_t client_ip, const simple_packet_t& current_packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes) { extern map_of_vector_counters_for_flow_t SubnetVectorMapFlow; extern std::mutex flow_counter_mutex; packed_conntrack_hash_t flow_tracking_structure; init_incoming_flow_counting_structure(flow_tracking_structure, current_packet); // convert this struct to 64 bit integer uint64_t connection_tracking_hash = convert_conntrack_hash_struct_to_integer(flow_tracking_structure); // logger << log4cpp::Priority::ERROR << "incoming flow: " << convert_ip_as_uint_to_string(client_ip) // << " packets " << sampled_number_of_packets << " bytes " << sampled_number_of_bytes << " hash " << connection_tracking_hash; { std::lock_guard lock_guard(flow_counter_mutex); conntrack_main_struct_t& current_element_flow = SubnetVectorMapFlow[client_ip]; if (current_packet.protocol == IPPROTO_TCP) { conntrack_key_struct_t& conntrack_key_struct = current_element_flow.in_tcp[connection_tracking_hash]; conntrack_key_struct.packets += sampled_number_of_packets; conntrack_key_struct.bytes += sampled_number_of_bytes; } else if (current_packet.protocol == IPPROTO_UDP) { conntrack_key_struct_t& conntrack_key_struct = current_element_flow.in_udp[connection_tracking_hash]; conntrack_key_struct.packets += sampled_number_of_packets; conntrack_key_struct.bytes += sampled_number_of_bytes; } } } // Creates compressed flow tracking structure void init_outgoing_flow_counting_structure(packed_conntrack_hash_t& flow_tracking_structure, const simple_packet_t& current_packet) { 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; } // Increment all flow counters using specified packet // increment_outgoing_flow_counters // client_ip in host byte order! void increment_outgoing_flow_counters(uint32_t client_ip, const simple_packet_t& current_packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes) { extern map_of_vector_counters_for_flow_t SubnetVectorMapFlow; extern std::mutex flow_counter_mutex; packed_conntrack_hash_t flow_tracking_structure; init_outgoing_flow_counting_structure(flow_tracking_structure, current_packet); // convert this struct to 64 bit integer uint64_t connection_tracking_hash = convert_conntrack_hash_struct_to_integer(flow_tracking_structure); // logger << log4cpp::Priority::ERROR << "outgoing flow: " << convert_ip_as_uint_to_string(client_ip) // << " packets " << sampled_number_of_packets << " bytes " << sampled_number_of_bytes << " hash " << connection_tracking_hash; { std::lock_guard lock_guard(flow_counter_mutex); conntrack_main_struct_t& current_element_flow = SubnetVectorMapFlow[client_ip]; if (current_packet.protocol == IPPROTO_TCP) { conntrack_key_struct_t& conntrack_key_struct = current_element_flow.out_tcp[connection_tracking_hash]; conntrack_key_struct.packets += sampled_number_of_packets; conntrack_key_struct.bytes += sampled_number_of_bytes; } else if (current_packet.protocol == IPPROTO_UDP) { conntrack_key_struct_t& conntrack_key_struct = current_element_flow.out_udp[connection_tracking_hash]; conntrack_key_struct.packets += sampled_number_of_packets; conntrack_key_struct.bytes += sampled_number_of_bytes; } } } // pretty print channel speed in pps and MBit std::string print_channel_speed_ipv6(std::string traffic_type, direction_t packet_direction) { uint64_t speed_in_pps = total_counters_ipv6.total_speed_average_counters[packet_direction].total.packets; uint64_t speed_in_bps = total_counters_ipv6.total_speed_average_counters[packet_direction].total.bytes; unsigned int number_of_tabs = 3; // We need this for correct alignment of blocks if (traffic_type == "Other traffic") { number_of_tabs = 4; } 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"; // Flows are not supported yet return stream.str(); } template void remove_orphaned_buckets(packet_buckets_storage_t& packet_storage, std::string protocol) { std::lock_guard lock_guard(packet_storage.packet_buckets_map_mutex); // List of buckets to remove std::vector buckets_to_remove; // logger << log4cpp::Priority::DEBUG << "We've got " << packet_storage->packet_buckets_map.size() << " packets buckets for processing"; // Find buckets for removal // We should not remove them here because it's tricky to do properly in C++ for (auto it = packet_storage.packet_buckets_map.begin(); it != packet_storage.packet_buckets_map.end(); ++it) { if (should_remove_orphaned_bucket(*it)) { logger << log4cpp::Priority::DEBUG << "We decided to remove " << protocol << " bucket " << convert_any_ip_to_string(it->first); buckets_to_remove.push_back(it->first); } } // logger << log4cpp::Priority::DEBUG << "We have " << buckets_to_remove.size() << " " << protocol << " orphaned buckets for cleanup"; for (auto client_ip : buckets_to_remove) { // Let's dump some data from it packet_bucket_t& bucket = packet_storage.packet_buckets_map[client_ip]; logger << log4cpp::Priority::WARN << "We've found orphaned bucket for IP: " << convert_any_ip_to_string(client_ip) << " it has " << bucket.parsed_packets_circular_buffer.size() << " parsed packets" << " and " << bucket.raw_packets_circular_buffer.size() << " raw packets" << " we will remove it"; // Stop packet collection ASAP bucket.we_could_receive_new_data = false; // Remove it completely from map packet_storage.packet_buckets_map.erase(client_ip); } return; } std::string get_attack_description_ipv6(subnet_ipv6_cidr_mask_t ipv6_address, const attack_details_t& current_attack) { std::stringstream attack_description; attack_description << "IP: " << print_ipv6_address(ipv6_address.subnet_address) << "\n"; attack_description << serialize_attack_description(current_attack) << "\n"; return attack_description.str(); } void execute_ipv6_ban(subnet_ipv6_cidr_mask_t ipv6_client, const attack_details_t& current_attack, const boost::circular_buffer& simple_packets_buffer, const boost::circular_buffer& raw_packets_buffer) { extern blackhole_ban_list_t ban_list_ipv6; // Execute ban actions ban_list_ipv6.add_to_blackhole(ipv6_client, current_attack); logger << log4cpp::Priority::INFO << "IPv6 address " << print_ipv6_cidr_subnet(ipv6_client) << " was banned"; uint32_t zero_ipv4_address = 0; call_blackhole_actions_per_host(attack_action_t::ban, zero_ipv4_address, ipv6_client, true, current_attack, attack_detection_source_t::Automatic, "", simple_packets_buffer, raw_packets_buffer); } void execute_ipv4_ban(uint32_t client_ip, const attack_details_t& current_attack, const std::string& flow_attack_details, const boost::circular_buffer& simple_packets_buffer, const boost::circular_buffer& raw_packets_buffer) { extern blackhole_ban_list_t ban_list_ipv4; // Execute ban actions ban_list_ipv4.add_to_blackhole(client_ip, current_attack); subnet_ipv6_cidr_mask_t zero_ipv6_address; call_blackhole_actions_per_host(attack_action_t::ban, client_ip, zero_ipv6_address, false, current_attack, attack_detection_source_t::Automatic, flow_attack_details, simple_packets_buffer, raw_packets_buffer); } // With this function we could get any element from our flow counter structure bool get_element_from_map_of_flow_counters(map_of_vector_counters_for_flow_t& map_of_counters, uint32_t client_ip, conntrack_main_struct_t& current_conntrack_structure) { extern std::mutex flow_counter_mutex; std::lock_guard lock_guard(flow_counter_mutex); current_conntrack_structure = map_of_counters[client_ip]; return true; } void process_filled_buckets_ipv4() { extern packet_buckets_storage_t packet_buckets_ipv4_storage; extern map_of_vector_counters_for_flow_t SubnetVectorMapFlow; std::vector filled_buckets; // TODO: amount of processing we do under lock is absolutely insane // We need to rework it std::lock_guard lock_guard(packet_buckets_ipv4_storage.packet_buckets_map_mutex); for (auto itr = packet_buckets_ipv4_storage.packet_buckets_map.begin(); itr != packet_buckets_ipv4_storage.packet_buckets_map.end(); ++itr) { // Find one time capture requests which filled completely if (itr->second.collection_pattern == collection_pattern_t::ONCE && itr->second.we_collected_full_buffer_least_once && !itr->second.is_already_processed) { logger << log4cpp::Priority::DEBUG << "Found filled bucket for IPv4 " << convert_any_ip_to_string(itr->first); filled_buckets.push_back(itr->first); } } // logger << log4cpp::Priority::DEBUG << "We have " << filled_buckets.size() << " filled buckets to process"; for (auto client_ip_as_integer : filled_buckets) { std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip_as_integer); packet_bucket_t& bucket = packet_buckets_ipv4_storage.packet_buckets_map[client_ip_as_integer]; // We found something, let's do processing logger << log4cpp::Priority::INFO << "We've got new completely filled bucket with packets for IP " << client_ip_as_string; std::string flow_attack_details = ""; if (enable_connection_tracking) { conntrack_main_struct_t current_conntrack_main_struct; bool get_flow_result = get_element_from_map_of_flow_counters(SubnetVectorMapFlow, fast_ntoh(client_ip_as_integer), current_conntrack_main_struct); if (get_flow_result) { flow_attack_details = print_flow_tracking_for_ip(current_conntrack_main_struct, client_ip_as_string); } else { logger << log4cpp::Priority::WARN << "Could not get flow structure address"; } } // Here I extract attack details saved at time when we crossed threshold attack_details_t current_attack = bucket.attack_details; // If we have no flow spec just do blackhole execute_ipv4_ban(client_ip_as_integer, current_attack, flow_attack_details, bucket.parsed_packets_circular_buffer, bucket.raw_packets_circular_buffer); // Mark it as processed. This will hide it from second call of same function bucket.is_already_processed = true; // Stop packet collection ASAP bucket.we_could_receive_new_data = false; // Remove it completely from map packet_buckets_ipv4_storage.packet_buckets_map.erase(client_ip_as_integer); } } void process_filled_buckets_ipv6() { std::lock_guard lock_guard(packet_buckets_ipv6_storage.packet_buckets_map_mutex); std::vector filled_buckets; for (auto itr = packet_buckets_ipv6_storage.packet_buckets_map.begin(); itr != packet_buckets_ipv6_storage.packet_buckets_map.end(); ++itr) { // Find one time capture requests which filled completely if (itr->second.collection_pattern == collection_pattern_t::ONCE && itr->second.we_collected_full_buffer_least_once && !itr->second.is_already_processed) { logger << log4cpp::Priority::DEBUG << "We have filled buckets for " << convert_any_ip_to_string(itr->first); filled_buckets.push_back(itr->first); } } // logger << log4cpp::Priority::DEBUG << "We have " << filled_buckets.size() << " filled buckets"; for (auto ipv6_address : filled_buckets) { logger << log4cpp::Priority::INFO << "We've got new completely filled bucket with packets for IPv6 " << print_ipv6_cidr_subnet(ipv6_address); packet_bucket_t* bucket = &packet_buckets_ipv6_storage.packet_buckets_map[ipv6_address]; // Here I extract attack details saved at time when we crossed threshold attack_details_t current_attack = bucket->attack_details; std::string basic_attack_information = get_attack_description_ipv6(ipv6_address, current_attack); // For IPv6 we support only blackhole at this moment. BGP Flow spec for IPv6 isn't so popular and we will skip implementation for some future execute_ipv6_ban(ipv6_address, current_attack, bucket->parsed_packets_circular_buffer, bucket->raw_packets_circular_buffer); // Mark it as processed. This will hide it from second call of same function bucket->is_already_processed = true; // Stop packet collection ASAP bucket->we_could_receive_new_data = false; // Remove it completely from map packet_buckets_ipv6_storage.packet_buckets_map.erase(ipv6_address); } } // This functions will check for packet buckets availible for processing void check_traffic_buckets() { extern packet_buckets_storage_t packet_buckets_ipv4_storage; while (true) { remove_orphaned_buckets(packet_buckets_ipv4_storage, "ipv4"); // Process buckets which haven't filled by packets remove_orphaned_buckets(packet_buckets_ipv6_storage, "ipv6"); process_filled_buckets_ipv4(); process_filled_buckets_ipv6(); boost::this_thread::sleep(boost::posix_time::seconds(check_for_availible_for_processing_packets_buckets)); } } // We use this function as callback for find_if to clean up orphaned buckets template bool should_remove_orphaned_bucket(const std::pair& pair) { logger << log4cpp::Priority::DEBUG << "Process bucket for " << convert_any_ip_to_string(pair.first); // We process only "once" buckets if (pair.second.collection_pattern != collection_pattern_t::ONCE) { logger << log4cpp::Priority::DEBUG << "We do not cleanup buckets with non-once collection pattern " << convert_any_ip_to_string(pair.first); return false; } std::chrono::duration elapsed_from_start_seconds = std::chrono::system_clock::now() - pair.second.collection_start_time; // We do cleanup for them in another function if (pair.second.we_collected_full_buffer_least_once) { logger << log4cpp::Priority::DEBUG << "We do not cleanup finished bucket for " << convert_any_ip_to_string(pair.first) << " it's " << elapsed_from_start_seconds.count() << " seconds old"; return false; } logger << log4cpp::Priority::DEBUG << "Bucket is " << elapsed_from_start_seconds.count() << " seconds old for " << convert_any_ip_to_string(pair.first) << " and has " << pair.second.parsed_packets_circular_buffer.size() << " parsed packets and " << pair.second.raw_packets_circular_buffer.size() << " raw packets"; if (elapsed_from_start_seconds.count() > maximum_time_since_bucket_start_to_remove) { logger << log4cpp::Priority::DEBUG << "We're going to remove bucket for " << convert_any_ip_to_string(pair.first) << " because it's too old"; return true; } return false; } bool get_statistics(std::vector& system_counters) { extern std::string total_simple_packets_processed_desc; extern std::string total_ipv6_packets_desc; extern std::string total_ipv4_packets_desc; extern std::string unknown_ip_version_packets_desc; extern std::string total_unparsed_packets_desc; extern std::string total_unparsed_packets_speed_desc; extern std::string speed_calculation_time_desc; extern std::string total_number_of_hosts_in_our_networks_desc; extern std::string influxdb_writes_total_desc; extern std::string influxdb_writes_failed_desc; system_counters.push_back(system_counter_t("total_simple_packets_processed", total_simple_packets_processed, metric_type_t::counter, total_simple_packets_processed_desc)); system_counters.push_back(system_counter_t("total_ipv4_packets", total_ipv4_packets, metric_type_t::counter, total_ipv4_packets_desc)); system_counters.push_back(system_counter_t("total_ipv6_packets", total_ipv6_packets, metric_type_t::counter, total_ipv6_packets_desc)); system_counters.push_back(system_counter_t("unknown_ip_version_packets", unknown_ip_version_packets, metric_type_t::counter, unknown_ip_version_packets_desc)); system_counters.push_back(system_counter_t("total_unparsed_packets", total_unparsed_packets, metric_type_t::counter, total_unparsed_packets_desc)); system_counters.push_back(system_counter_t("total_unparsed_packets_speed", total_unparsed_packets_speed, metric_type_t::gauge, total_unparsed_packets_speed_desc)); system_counters.push_back(system_counter_t("speed_recalculation_time_seconds", speed_calculation_time.tv_sec, metric_type_t::gauge, speed_calculation_time_desc)); system_counters.push_back(system_counter_t("speed_recalculation_time_microseconds", speed_calculation_time.tv_usec, metric_type_t::gauge, speed_calculation_time_desc)); system_counters.push_back(system_counter_t("total_number_of_hosts", total_number_of_hosts_in_our_networks, metric_type_t::gauge, total_number_of_hosts_in_our_networks_desc)); system_counters.push_back(system_counter_t("influxdb_writes_total", influxdb_writes_total, metric_type_t::counter, influxdb_writes_total_desc)); system_counters.push_back(system_counter_t("influxdb_writes_failed", influxdb_writes_failed, metric_type_t::counter, influxdb_writes_failed_desc)); if (fastnetmon_global_configuration.sflow) { auto sflow_stats = get_sflow_stats(); system_counters.insert(system_counters.end(), sflow_stats.begin(), sflow_stats.end()); } if (fastnetmon_global_configuration.netflow) { auto netflow_stats = get_netflow_stats(); system_counters.insert(system_counters.end(), netflow_stats.begin(), netflow_stats.end()); } #ifdef FASTNETMON_ENABLE_AFPACKET if (fastnetmon_global_configuration.mirror_afpacket) { auto af_packet_counters = get_af_packet_stats(); system_counters.insert(system_counters.end(), af_packet_counters.begin(), af_packet_counters.end()); } #endif return true; } // Generates human readable comma separated list of enabled traffic capture plugins std::vector generate_list_of_enabled_capture_engines() { std::vector list; if (configuration_map.count("sflow") != 0 && configuration_map["sflow"] == "on") { list.push_back("sflow"); } if (configuration_map.count("netflow") != 0 && configuration_map["netflow"] == "on") { list.push_back("netflow"); } if (configuration_map.count("mirror_afpacket") != 0 && configuration_map["mirror_afpacket"] == "on") { list.push_back("af_packet"); } if (configuration_map.count("mirror_afxdp") != 0 && configuration_map["mirror_afxdp"] == "on") { list.push_back("af_xdp"); } return list; } // Reads instance_id from filesystem bool get_instance_id(std::string& instance_id) { std::string instance_id_path = "/var/lib/instance_id.fst"; // Not found and that's OK if (!file_exists(instance_id_path)) { return false; } // It has no newline inside if (!read_file_to_string(instance_id_path, instance_id)) { return false; } return true; } void send_usage_data_to_reporting_server() { extern std::string reporting_server; extern total_speed_counters_t total_counters_ipv4; extern total_speed_counters_t total_counters_ipv6; // Build query std::stringstream request_stream; request_stream << "https://" << reporting_server << "/stats_v1"; std::string stats_json_string; try { nlohmann::json stats; uint64_t incoming_ipv4 = total_counters_ipv4.total_speed_average_counters[INCOMING].total.bytes; uint64_t outgoing_ipv4 = total_counters_ipv4.total_speed_average_counters[OUTGOING].total.bytes; uint64_t incoming_ipv6 = total_counters_ipv6.total_speed_average_counters[INCOMING].total.bytes; uint64_t outgoing_ipv6 = total_counters_ipv6.total_speed_average_counters[OUTGOING].total.bytes; stats["incoming_traffic_speed"] = incoming_ipv4 + incoming_ipv6; stats["outgoing_traffic_speed"] = outgoing_ipv4 + outgoing_ipv6; stats["incoming_traffic_speed_ipv4"] = incoming_ipv4; stats["outgoing_traffic_speed_ipv4"] = outgoing_ipv4; stats["incoming_traffic_speed_ipv6"] = incoming_ipv6; stats["outgoing_traffic_speed_ipv6"] = outgoing_ipv6; stats["flows_speed"] = netflow_ipfix_all_protocols_total_flows_speed; stats["headers_speed"] = sflow_raw_packet_headers_total_speed; stats["total_hosts"] = total_number_of_hosts_in_our_networks; stats["cap_plugins"] = generate_list_of_enabled_capture_engines(); stats["speed_calc_time"] = speed_calculation_time.tv_sec; stats["version"] = fastnetmon_platform_configuration.fastnetmon_version; stats["virt_method"] = get_virtualisation_method(); // We use statically allocated counters in that case stats["hosts_hash_ipv4"] = total_number_of_hosts_in_our_networks; ssize_t hosts_hash_size_ipv6 = 0; { std::lock_guard lock_guard(ipv6_host_counters.counter_map_mutex); hosts_hash_size_ipv6 = ipv6_host_counters.average_speed_map.size(); } stats["hosts_hash_ipv6"] = hosts_hash_size_ipv6; bool gobgp = false; if (configuration_map.count("gobgp") != 0 && configuration_map["gobgp"] == "on") { gobgp = true; } stats["bgp"] = gobgp; stats["bgp_flow_spec"] = false; bool influxdb = false; if (configuration_map.count("influxdb") != 0 && configuration_map["influxdb"] == "on") { influxdb = true; } stats["influxdb"] = influxdb; stats["clickhouse_metrics"] = false; stats["traffic_db"] = false; stats["prometheus"] = false; std::string cpu_model; get_cpu_model(cpu_model); stats["cpu_model"] = cpu_model; stats["cpu_logical_cores"] = get_logical_cpus_number(); // Mbytes stats["memory_size"] = get_total_memory(); std::string kernel_version = "unknown"; if (!get_kernel_version(kernel_version)) { logger << log4cpp::Priority::ERROR << "Cannot get Linux kernel version"; } stats["kernel_version"] = kernel_version; std::vector cpu_flags; if (!get_cpu_flags(cpu_flags)) { logger << log4cpp::Priority::ERROR << "Cannot get CPU flags"; } stats["cpu_flags"] = cpu_flags; std::string linux_distro_name = "unknown"; if (!get_linux_distro_name(linux_distro_name)) { logger << log4cpp::Priority::ERROR << "Cannot get Linux distro name"; } stats["linux_distro_name"] = linux_distro_name; std::string linux_distro_version = "unknown"; if (!get_linux_distro_version(linux_distro_version)) { logger << log4cpp::Priority::ERROR << "Cannot get Linux distro version"; } stats["linux_distro_version"] = linux_distro_version; std::string instance_id; if (get_instance_id(instance_id)) { stats["instance_id"] = instance_id; } else { // OK, it's optional } stats_json_string = stats.dump(); } catch (...) { logger << log4cpp::Priority::ERROR << "Failed to serialise stats"; return; } // It's fair to show but we will expose our delay. We need to make delay random first // logger << log4cpp::Priority::DEBUG << "Preparing to send following information to telemetry server " << request_stream.str(); uint32_t response_code = 0; std::string response_body; std::string error_text; std::map headers; // I think we need to do it to make clear about format for remote app headers["Content-Type"] = "application/json"; // Just do it to know about DNS issues, execute_web_request can do DNS resolution on it's own std::string reporting_server_ip_address = dns_lookup(reporting_server); if (reporting_server_ip_address.empty()) { logger << log4cpp::Priority::DEBUG << "Stats server resolver failed, please check your DNS"; return; } bool result = execute_web_request_secure(request_stream.str(), "post", stats_json_string, response_code, response_body, headers, error_text); if (!result) { logger << log4cpp::Priority::DEBUG << "Can't collect stats data"; return; } if (response_code != 200) { logger << log4cpp::Priority::DEBUG << "Got code " << response_code << " from stats server instead of 200"; return; } } void collect_stats() { extern unsigned int stats_thread_initial_call_delay; extern unsigned int stats_thread_sleep_time; 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)); } } // Adds total traffic metrics to Prometheus endpoint void add_total_traffic_to_prometheus(const total_speed_counters_t& total_counters, std::stringstream& output, const std::string& protocol_version) { std::vector directions = { INCOMING, OUTGOING, INTERNAL, OTHER }; for (auto packet_direction : directions) { std::string direction_as_string = get_direction_name(packet_direction); // Packets std::string packet_metric_name = "fastnetmon_total_traffic_packets"; output << "# HELP Total traffic in packets\n"; output << "# TYPE " << packet_metric_name << " gauge\n"; output << packet_metric_name << "{traffic_direction=\"" << direction_as_string << "\",protocol_version=\"" << protocol_version << "\"} " << total_counters.total_speed_average_counters[packet_direction].total.packets << "\n"; // Bytes std::string bits_metric_name = "fastnetmon_total_traffic_bits"; output << "# HELP Total traffic in bits\n"; output << "# TYPE " << bits_metric_name << " gauge\n"; output << bits_metric_name << "{traffic_direction=\"" << direction_as_string << "\",protocol_version=\"" << protocol_version << "\"} " << total_counters.total_speed_average_counters[packet_direction].total.bytes * 8 << "\n"; // Flows if (protocol_version == "ipv4" && enable_connection_tracking && (packet_direction == INCOMING || packet_direction == OUTGOING)) { uint64_t flow_rate = 0; std::string flows_metric_name = "fastnetmon_total_traffic_flows"; if (packet_direction == INCOMING) { flow_rate = incoming_total_flows_speed; } else if (packet_direction == OUTGOING) { flow_rate = outgoing_total_flows_speed; } output << "# HELP Total traffic in flows\n"; output << "# TYPE " << flows_metric_name << " gauge\n"; output << flows_metric_name << "{traffic_direction=\"" << direction_as_string << "\",protocol_version=\"" << protocol_version << "\"}" << flow_rate << "\n"; } } } // This function produces an HTTP response for the given // request. The type of the response object depends on the // contents of the request, so the interface requires the // caller to pass a generic lambda for receiving the response. template void handle_prometheus_http_request(boost::beast::http::request>&& req, Send&& send) { // Returns a bad request response auto const bad_request = [&req](boost::beast::string_view why) { boost::beast::http::response res{ boost::beast::http::status::bad_request, req.version() }; res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); res.set(boost::beast::http::field::content_type, "text/html"); res.keep_alive(req.keep_alive()); res.body() = std::string(why); res.prepare_payload(); return res; }; // Returns a not found response auto const not_found = [&req](boost::beast::string_view target) { boost::beast::http::response res{ boost::beast::http::status::not_found, req.version() }; res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); res.set(boost::beast::http::field::content_type, "text/html"); res.keep_alive(req.keep_alive()); res.body() = "The resource '" + std::string(target) + "' was not found."; res.prepare_payload(); return res; }; // Returns a server error response auto const server_error = [&req](boost::beast::string_view what) { boost::beast::http::response res{ boost::beast::http::status::internal_server_error, req.version() }; res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); res.set(boost::beast::http::field::content_type, "text/html"); res.keep_alive(req.keep_alive()); res.body() = "An error occurred: '" + std::string(what) + "'"; res.prepare_payload(); return res; }; // Make sure we can handle the method if (req.method() != boost::beast::http::verb::get) { return send(bad_request("Unknown HTTP-method")); } // We support only /metrics URL if (req.target() != "/metrics") { return send(not_found(req.target())); } // Respond to GET request boost::beast::http::response res{ boost::beast::http::status::ok, req.version() }; res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); res.set(boost::beast::http::field::content_type, "text/html"); std::vector system_counters; // Application statistics bool result = get_statistics(system_counters); if (!result) { return send(server_error("Could not get application statistics")); } std::stringstream output; for (auto counter : system_counters) { std::string metric_type = "counter"; if (counter.counter_type == metric_type_t::gauge) { metric_type = "gauge"; } // It's good idea to add proper descriptions in future output << "# HELP " << counter.counter_description << "\n"; output << "# TYPE " << "fastnetmon_" << counter.counter_name << " " << metric_type << "\n"; output << "fastnetmon_" << counter.counter_name << " " << counter.counter_value << "\n"; } extern total_speed_counters_t total_counters_ipv4; // Add total traffic metrics add_total_traffic_to_prometheus(total_counters_ipv4, output, "ipv4"); extern total_speed_counters_t total_counters_ipv6; add_total_traffic_to_prometheus(total_counters_ipv6, output, "ipv6"); res.body() = output.str(); res.keep_alive(req.keep_alive()); res.prepare_payload(); return send(std::move(res)); } // This is the C++11 equivalent of a generic lambda. // The function object is used to send an HTTP message. template struct send_lambda { Stream& stream_; bool& close_; boost::beast::error_code& ec_; explicit send_lambda(Stream& stream, bool& close, boost::beast::error_code& ec) : stream_(stream), close_(close), ec_(ec) { } template void operator()(boost::beast::http::message&& msg) const { // Determine if we should close the connection after close_ = msg.need_eof(); // We need the serialiser here because the serialiser requires // a non-const file_body, and the message oriented version of // http::write only works with const messages. boost::beast::http::serializer sr{ msg }; boost::beast::http::write(stream_, sr, ec_); } }; // handled http query to Prometheus endpoint void do_prometheus_http_session(boost::asio::ip::tcp::socket& socket) { bool close = false; boost::beast::error_code ec; // This buffer is required to persist across reads boost::beast::flat_buffer buffer; // This lambda is used to send messages send_lambda lambda{ socket, close, ec }; while (true) { // Read a request boost::beast::http::request req; boost::beast::http::read(socket, buffer, req, ec); if (ec == boost::beast::http::error::end_of_stream) { break; } if (ec) { logger << log4cpp::Priority::ERROR << "HTTP query read failed: " << ec.message(); return; } // Send the response handle_prometheus_http_request(std::move(req), lambda); if (ec) { logger << log4cpp::Priority::ERROR << "HTTP query read failed: " << ec.message(); return; } if (close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. break; } } // Send a TCP shutdown socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); // At this point the connection is closed gracefully } void start_prometheus_web_server() { extern unsigned short prometheus_port; extern std::string prometheus_host; try { logger << log4cpp::Priority::INFO << "Starting Prometheus endpoint on " << prometheus_host << ":" << prometheus_port; auto const address = boost::asio::ip::make_address(prometheus_host); auto const port = static_cast(prometheus_port); // The io_context is required for all I/O boost::asio::io_context ioc{ 1 }; // The acceptor receives incoming connections boost::asio::ip::tcp::acceptor acceptor{ ioc, { address, port } }; while (true) { // This will receive the new connection boost::asio::ip::tcp::socket socket{ ioc }; // Block until we get a connection acceptor.accept(socket); // Launch the session, transferring ownership of the socket std::thread{ std::bind(&do_prometheus_http_session, std::move(socket)) }.detach(); } } catch (const std::exception& e) { logger << log4cpp::Priority::ERROR << "Prometheus server exception: " << e.what(); } } std::string get_human_readable_attack_detection_direction(attack_detection_direction_type_t attack_detection_direction) { if (attack_detection_direction == attack_detection_direction_type_t::unknown) { return "unknown"; } else if (attack_detection_direction == attack_detection_direction_type_t::incoming) { return "incoming"; } else if (attack_detection_direction == attack_detection_direction_type_t::outgoing) { return "outgoing"; } else { return "unknown"; } } // Sends attack information to reporting server void send_attack_data_to_reporting_server(const std::string& attack_json_string) { extern std::string reporting_server; // Build query std::stringstream request_stream; request_stream << "https://" << reporting_server << "/attacks_v1"; uint32_t response_code = 0; std::string response_body; std::string error_text; std::map headers; // I think we need to do it to make clear about format for remote app headers["Content-Type"] = "application/json"; // Just do it to know about DNS issues, execute_web_request can do DNS resolution on it's own std::string reporting_server_ip_address = dns_lookup(reporting_server); if (reporting_server_ip_address.empty()) { logger << log4cpp::Priority::DEBUG << "Stats server resolver failed, please check your DNS"; return; } bool result = execute_web_request_secure(request_stream.str(), "post", attack_json_string, response_code, response_body, headers, error_text); if (!result) { logger << log4cpp::Priority::DEBUG << "Can't collect attack stats data"; return; } if (response_code != 200) { logger << log4cpp::Priority::DEBUG << "Got code " << response_code << " from stats server instead of 200"; return; } } fastnetmon-1.2.8/src/fastnetmon_logic.hpp000066400000000000000000000140121472727706000205450ustar00rootroot00000000000000#include "bgp_protocol.hpp" #include "fastnetmon_types.hpp" #ifdef REDIS #include #endif #include "all_logcpp_libraries.hpp" #include "packet_bucket.hpp" //#include "fastnetmon.grpc.pb.h" //#include 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 we_should_ban_this_entity(const subnet_counter_t& average_speed_element, const ban_settings_t& current_ban_settings, attack_detection_threshold_type_t& attack_detection_source, attack_detection_direction_type_t& attack_detection_direction); bool exceed_mbps_speed(uint64_t in_counter, uint64_t out_counter, unsigned int threshold_mbps); bool exceed_flow_speed(uint64_t in_counter, uint64_t out_counter, unsigned int threshold); bool exceed_pps_speed(uint64_t in_counter, uint64_t out_counter, unsigned int threshold); ban_settings_t read_ban_settings(configuration_map_t configuration_map, std::string host_group_name); logging_configuration_t read_logging_settings(configuration_map_t configuration_map); void print_attack_details_to_file(const std::string& details, const std::string& client_ip_as_string, const attack_details_t& current_attack); std::string print_ban_thresholds(ban_settings_t current_ban_settings); std::string print_subnet_ipv4_load(); std::string print_flow_tracking_for_ip(conntrack_main_struct_t& conntrack_element, std::string client_ip); std::string print_flow_tracking_for_specified_protocol(contrack_map_type& protocol_map, std::string client_ip, direction_t flow_direction); void convert_integer_to_conntrack_hash_struct(const uint64_t& packed_connection_data, packed_conntrack_hash_t& unpacked_data); void cleanup_ban_list(); std::string print_ddos_attack_details(); std::string get_attack_description(uint32_t client_ip, const attack_details_t& current_attack); std::string get_attack_description_in_json(uint32_t client_ip, const attack_details_t& current_attack); std::string generate_simple_packets_dump(std::vector& ban_list_details); void send_attack_details(uint32_t client_ip, attack_details_t current_attack_details); void call_attack_details_handlers(uint32_t client_ip, attack_details_t& current_attack, std::string attack_fingerprint); uint64_t convert_conntrack_hash_struct_to_integer(packed_conntrack_hash_t* struct_value); bool process_flow_tracking_table(conntrack_main_struct_t& conntrack_element, std::string client_ip); bool exec_with_stdin_params(std::string cmd, std::string params); ban_settings_t get_ban_settings_for_this_subnet(const subnet_cidr_mask_t& subnet, 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); #ifdef REDIS void store_data_in_redis(std::string key_name, std::string attack_details); redisContext* redis_init_connection(); #endif void execute_ip_ban(uint32_t client_ip, subnet_counter_t average_speed_element, std::string flow_attack_details, subnet_cidr_mask_t customer_subnet); void call_blackhole_actions_per_host(attack_action_t attack_action, uint32_t client_ip, const subnet_ipv6_cidr_mask_t& client_ipv6, bool ipv6, const attack_details_t& current_attack, attack_detection_source_t attack_detection_source, const std::string& flow_attack_details, const boost::circular_buffer& simple_packets_buffer, const boost::circular_buffer& raw_packets_buffer); #ifdef MONGO void store_data_in_mongo(std::string key_name, std::string attack_details_json); #endif std::string print_channel_speed_ipv6(std::string traffic_type, direction_t packet_direction); std::string print_channel_speed(std::string traffic_type, direction_t packet_direction); void traffic_draw_ipv4_program(); void recalculate_speed(); std::string draw_table_ipv4(const attack_detection_direction_type_t& sort_direction, const attack_detection_threshold_type_t& sorter_type); std::string draw_table_ipv6(attack_detection_direction_type_t sort_direction, attack_detection_threshold_type_t sorter_type); std::string draw_table_ipv4_hash(attack_detection_direction_type_t sort_direction, attack_detection_threshold_type_t sorter_type); void print_screen_contents_into_file(std::string screen_data_stats_param, std::string file_path); void zeroify_all_flow_counters(); void process_packet(simple_packet_t& current_packet); void system_counters_speed_thread_handler(); void increment_outgoing_flow_counters( uint32_t client_ip, const simple_packet_t& packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes); void increment_incoming_flow_counters( uint32_t client_ip, const simple_packet_t& packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes); void traffic_draw_ipv6_program(); void check_traffic_buckets(); void process_filled_buckets_ipv6(); template bool should_remove_orphaned_bucket(const std::pair& pair); void inaccurate_time_generator(); void collect_stats(); void start_prometheus_web_server(); std::string get_human_readable_attack_detection_direction(attack_detection_direction_type_t attack_detection_direction); void send_attack_data_to_reporting_server(const std::string& attack_json_string); fastnetmon-1.2.8/src/fastnetmon_logrotate000066400000000000000000000002021472727706000206560ustar00rootroot00000000000000/var/log/fastnetmon.log { rotate 31 daily size 10M missingok copytruncate notifempty delaycompress } fastnetmon-1.2.8/src/fastnetmon_networks.hpp000066400000000000000000000135431472727706000213340ustar00rootroot00000000000000#pragma once #include #ifdef _WIN32 #include #include // in6_addr #else #include #include #endif #include #include // IPv6 subnet with mask in cidr form class subnet_ipv6_cidr_mask_t { public: void set_cidr_prefix_length(uint32_t cidr_prefix_length) { this->cidr_prefix_length = cidr_prefix_length; } // Just copy this data by pointer void set_subnet_address(const in6_addr* ipv6_host_address_param) { memcpy(&subnet_address, ipv6_host_address_param, sizeof(in6_addr)); } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { // Boost does not know how to serialize in6_addr but nested s6_addr is a just array with 16 elements of char and we do serialize it instead ar& BOOST_SERIALIZATION_NVP(subnet_address.s6_addr); ar& BOOST_SERIALIZATION_NVP(cidr_prefix_length); } in6_addr subnet_address{}; uint32_t cidr_prefix_length = 128; }; // We need this operator because we are using this class in std::map which // requires ordering // We use inline to suppress angry compiler inline bool operator<(const subnet_ipv6_cidr_mask_t& lhs, const subnet_ipv6_cidr_mask_t& rhs) { if (lhs.cidr_prefix_length < rhs.cidr_prefix_length) { return true; } else if (lhs.cidr_prefix_length == rhs.cidr_prefix_length) { // Compare addresses as memory blocks // Order may be incorrect (descending vs ascending) return memcmp(&lhs.subnet_address, &rhs.subnet_address, sizeof(in6_addr)) < 0; } else { return false; } } // Inject custom specialization of std::hash in namespace std // We need it for std::unordered_map namespace std { template <> struct hash { typedef std::size_t result_type; std::size_t operator()(const subnet_ipv6_cidr_mask_t& s) const { std::size_t seed = 0; const uint8_t* b = s.subnet_address.s6_addr; boost::hash_combine(seed, s.cidr_prefix_length); // Add all elements from IPv6 into hash for (int i = 0; i < 16; i++) { boost::hash_combine(seed, b[i]); } return seed; } }; } // namespace std inline bool operator==(const subnet_ipv6_cidr_mask_t& lhs, const subnet_ipv6_cidr_mask_t& rhs) { // Prefixes has different lengths if (lhs.cidr_prefix_length != rhs.cidr_prefix_length) { return false; } return memcmp(&lhs.subnet_address, &rhs.subnet_address, sizeof(in6_addr)) == 0; } inline bool operator!=(const subnet_ipv6_cidr_mask_t& lhs, const subnet_ipv6_cidr_mask_t& rhs) { return !(lhs == rhs); } // Compares for standard IPv6 structure inline bool operator==(const in6_addr& lhs, const in6_addr& rhs) { return memcmp(&lhs, &rhs, sizeof(in6_addr)) == 0; } inline bool operator!=(const in6_addr& lhs, const in6_addr& rhs) { return !(lhs == rhs); } // Subnet with cidr mask class subnet_cidr_mask_t { public: subnet_cidr_mask_t() { this->subnet_address = 0; this->cidr_prefix_length = 0; } subnet_cidr_mask_t(uint32_t subnet_address, uint32_t cidr_prefix_length) { this->subnet_address = subnet_address; this->cidr_prefix_length = cidr_prefix_length; } // We need this operator because we are using this class in std::map which // requires order bool operator<(const subnet_cidr_mask_t& rhs) { if (this->cidr_prefix_length < rhs.cidr_prefix_length) { return true; } else if (this->cidr_prefix_length == rhs.cidr_prefix_length) { return this->subnet_address < rhs.subnet_address; } else { return false; } } bool is_zero_subnet() { if (subnet_address == 0 && cidr_prefix_length == 0) { return true; } else { return false; } } void set_subnet_address(uint32_t subnet_address) { this->subnet_address = subnet_address; } void set_cidr_prefix_length(uint32_t cidr_prefix_length) { this->cidr_prefix_length = cidr_prefix_length; } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(subnet_address); ar& BOOST_SERIALIZATION_NVP(cidr_prefix_length); } // Big endian (network byte order) uint32_t subnet_address = 0; // Little endian uint32_t cidr_prefix_length = 0; }; inline bool operator==(const subnet_cidr_mask_t& lhs, const subnet_cidr_mask_t& rhs) { // Prefixes has different lengths if (lhs.cidr_prefix_length != rhs.cidr_prefix_length) { return false; } return lhs.subnet_address == rhs.subnet_address; } inline bool operator!=(const subnet_cidr_mask_t& lhs, const subnet_cidr_mask_t& rhs) { return !(lhs == rhs); } // We need free function for comparison code inline bool operator<(const subnet_cidr_mask_t& lhs, const subnet_cidr_mask_t& rhs) { if (lhs.cidr_prefix_length < rhs.cidr_prefix_length) { return true; } else if (lhs.cidr_prefix_length == rhs.cidr_prefix_length) { return lhs.subnet_address < rhs.subnet_address; } else { return false; } } namespace std { // Inject custom specialization of std::hash in namespace std // We need it for std::unordered_map template <> struct hash { typedef std::size_t result_type; std::size_t operator()(const subnet_cidr_mask_t& s) const { std::size_t seed = 0; boost::hash_combine(seed, s.cidr_prefix_length); boost::hash_combine(seed, s.subnet_address); return seed; } }; } // namespace std // This class can keep any network class subnet_ipv4_ipv6_cidr_t { public: subnet_cidr_mask_t ipv4{}; subnet_ipv6_cidr_mask_t ipv6{}; bool is_ipv6 = false; }; fastnetmon-1.2.8/src/fastnetmon_pcap_format.cpp000066400000000000000000000055471472727706000217530ustar00rootroot00000000000000#include "fastnetmon_pcap_format.hpp" #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; } fastnetmon_pcap_file_header_t pcap_header{}; ssize_t file_header_readed_bytes = read(filedesc, &pcap_header, sizeof(fastnetmon_pcap_file_header_t)); if (file_header_readed_bytes != sizeof(fastnetmon_pcap_file_header_t)) { 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); fastnetmon_pcap_pkthdr_t pcap_packet_header; ssize_t packet_header_readed_bytes = read(filedesc, &pcap_packet_header, sizeof(fastnetmon_pcap_pkthdr_t)); if (packet_header_readed_bytes != sizeof(fastnetmon_pcap_pkthdr_t)) { // 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; } // Move this code to constructor bool fill_pcap_header(fastnetmon_pcap_file_header_t* pcap_header, uint32_t snap_length) { pcap_header->magic = 0xa1b2c3d4; pcap_header->version_major = 2; pcap_header->version_minor = 4; pcap_header->thiszone = 0; pcap_header->sigfigs = 0; // Maximum really captured (not original packet) length for this file pcap_header->snaplen = snap_length; // http://www.tcpdump.org/linktypes.html // DLT_EN10MB = 1 pcap_header->linktype = 1; return true; } fastnetmon-1.2.8/src/fastnetmon_pcap_format.hpp000066400000000000000000000130301472727706000217420ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fastnetmon_simple_packet.hpp" /* pcap dump format: global header: pcap_file_header packet header: fastnetmon_pcap_pkthdr_t */ // We use copy and paste from pcap.h here because we do not want to link with // pcap here class __attribute__((__packed__)) fastnetmon_pcap_file_header_t { public: uint32_t magic = 0; uint16_t version_major = 0; uint16_t version_minor = 0; int32_t thiszone = 0; /* gmt to local correction */ uint32_t sigfigs = 0; /* accuracy of timestamps */ uint32_t snaplen = 0; /* max length saved portion of each pkt */ uint32_t linktype = 0; /* data link type (LINKTYPE_*) */ }; static_assert(sizeof(fastnetmon_pcap_file_header_t) == 24, "Bad size for fastnetmon_pcap_file_header_t"); // Link types for PCAP which we use in FastNetMon #define FASTNETMON_PCAP_LINKTYPE_ETHERNET 1 #define FASTNETMON_PCAP_LINKTYPE_LINUX_SLL 113 // We can't use pcap_pkthdr from upstream because it uses 16 bytes timeval // instead of 8 byte and // broke everything class __attribute__((__packed__)) fastnetmon_pcap_pkthdr_t { public: uint32_t ts_sec = 0; /* timestamp seconds */ uint32_t ts_usec = 0; /* timestamp microseconds */ uint32_t incl_len = 0; /* number of octets of packet saved in file */ uint32_t orig_len = 0; /* actual length of packet */ }; static_assert(sizeof(fastnetmon_pcap_pkthdr_t) == 16, "Bad size for fastnetmon_pcap_pkthdr_t"); // This class consist of pcap header and payload in same place class pcap_packet_information_t { public: uint32_t ts_sec = 0; /* timestamp seconds */ uint32_t ts_usec = 0; /* timestamp microseconds */ uint32_t incl_len = 0; uint32_t orig_len = 0; char* data_pointer = nullptr; }; 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(fastnetmon_pcap_file_header_t* pcap_header, uint32_t snap_length); // Class for very convenient pcap file reading class pcap_roller_t { public: pcap_roller_t(const std::string& pcap_file_path) { this->pcap_file_path = pcap_file_path; } ~pcap_roller_t() { if (filedesc > 0) { close(filedesc); } if (packet_buffer) { free(packet_buffer); packet_buffer = nullptr; } } bool open() { extern log4cpp::Category& logger; filedesc = ::open(pcap_file_path.c_str(), O_RDONLY); if (filedesc <= 0) { logger << log4cpp::Priority::ERROR << "Can't open dump file, error: " << strerror(errno); return false; } ssize_t file_header_readed_bytes = read(filedesc, &pcap_header, sizeof(fastnetmon_pcap_file_header_t)); if (file_header_readed_bytes != sizeof(fastnetmon_pcap_file_header_t)) { logger << log4cpp::Priority::ERROR << "Can't read pcap file header"; return false; } // http://www.tcpdump.org/manpages/pcap-savefile.5.html if (!(pcap_header.magic == 0xa1b2c3d4 or pcap_header.magic == 0xd4c3b2a1)) { logger << log4cpp::Priority::ERROR << "Magic in file header broken"; return false; } // Allocate read buffer packet_buffer = (char*)malloc(pcap_header.snaplen); return true; } // Read on more packet from stream and returns false if we run out of packets bool read_next(pcap_packet_information_t& pcap_packet_information) { extern log4cpp::Category& logger; fastnetmon_pcap_pkthdr_t pcap_packet_header; ssize_t packet_header_readed_bytes = read(filedesc, &pcap_packet_header, sizeof(fastnetmon_pcap_pkthdr_t)); if (packet_header_readed_bytes != sizeof(fastnetmon_pcap_pkthdr_t)) { // We have no more packets to read return false; } if (pcap_packet_header.incl_len > pcap_header.snaplen) { logger << log4cpp::Priority::ERROR << "Captured packet size for this dump exceed limit for pcap file"; return false; } // Read included part of raw packet from file 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 << "We successfully read packet header but can't read packet payload"; return false; } pcap_packet_information.incl_len = pcap_packet_header.incl_len; pcap_packet_information.orig_len = pcap_packet_header.orig_len; pcap_packet_information.ts_sec = pcap_packet_header.ts_sec; pcap_packet_information.ts_usec = pcap_packet_header.ts_usec; pcap_packet_information.data_pointer = packet_buffer; return true; } public: fastnetmon_pcap_file_header_t pcap_header{}; private: std::string pcap_file_path = ""; int filedesc = 0; char* packet_buffer = nullptr; }; fastnetmon-1.2.8/src/fastnetmon_plugin.hpp000066400000000000000000000006421472727706000207520ustar00rootroot00000000000000#pragma once // This file consist of all important plugins which could be usefult for plugin // development // For support uint32_t, uint16_t #include #include "all_logcpp_libraries.hpp" #include "fast_library.hpp" #include "fast_platform.hpp" // Get log4cpp logger from main programme extern log4cpp::Category& logger; // Access to inaccurate but fast time extern time_t current_inaccurate_time; fastnetmon-1.2.8/src/fastnetmon_simple_packet.hpp000066400000000000000000000066641472727706000223060ustar00rootroot00000000000000#pragma once #include #ifdef _WIN32 #include #include // in6_addr #else #include // in6_addr #include #endif #include enum direction_t { INCOMING = 0, OUTGOING = 1, INTERNAL = 2, OTHER = 3 }; enum source_t { UNKNOWN = 0, MIRROR = 1, SFLOW = 2, NETFLOW = 3, TERAFLOW = 4 }; // Forwarding status of packet // IPFIX: https://datatracker.ietf.org/doc/html/rfc7270#section-4.12 // Netflow v9: https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html enum class forwarding_status_t { unknown, forwarded, dropped, consumed }; // Our internal representation of all packet types class simple_packet_t { public: // Source plugin for this traffic type source_t source = UNKNOWN; // Sampling rate uint32_t sample_ratio = 1; // IPv4 in big endian, network byte order uint32_t src_ip = 0; uint32_t dst_ip = 0; // IPv6 addresses in6_addr src_ipv6{}; in6_addr dst_ipv6{}; uint8_t source_mac[6]{}; uint8_t destination_mac[6]{}; // ASNs uint32_t src_asn = 0; uint32_t dst_asn = 0; // Countries // These strings are statically allocated and do not use dynamic memory boost::beast::static_string<2> src_country; boost::beast::static_string<2> dst_country; // Physical port numbers from network equipment uint32_t input_interface = 0; uint32_t output_interface = 0; // IP protocol version: IPv4 or IPv6 uint8_t ip_protocol_version = 4; uint8_t ttl = 0; uint16_t source_port = 0; uint16_t destination_port = 0; uint32_t protocol = 0; uint64_t length = 0; // The number of octets includes IP header(s) and IP payload. // We use it in addition to length because flow spec rule needs exactly it uint64_t ip_length = 0; // Any single simple flow may have multiple packets. It happens for all flow based protocols uint64_t number_of_packets = 1; // TCP flags uint8_t flags = 0; // If IP packet fragmented bool ip_fragmented = false; // We will have more fragments bool ip_more_fragments = false; // If IP has don't fragment flag bool ip_dont_fragment = false; // Fragment offset in bytes when fragmentation involved uint16_t ip_fragment_offset = 0; // Time when we actually received this packet, we use quite rough and inaccurate but very fast time source for it time_t arrival_time = 0; // Timestamp of packet as reported by Netflow or IPFIX agent on device, it may be very inaccurate as nobody cares about time on equipment struct timeval ts = { 0, 0 }; const void* payload_pointer = nullptr; // Part of packet we captured from wire. It may not be full length of packet int32_t captured_payload_length = 0; // Full length of packet we observed. It may be larger then packet_captured_payload_length in case of cropped mirror or sFlow traffic uint32_t payload_full_length = 0; // Forwarding status forwarding_status_t forwarding_status = forwarding_status_t::unknown; // vlan tag if we can extract it uint32_t vlan = 0; // Device uptime when flow started int64_t flow_start = 0; // Device uptime when flow finished int64_t flow_end = 0; direction_t packet_direction = OTHER; // IP address of device which send this flow uint32_t agent_ip_address = 0; }; fastnetmon-1.2.8/src/fastnetmon_tests.cpp000066400000000000000000000076761472727706000206270ustar00rootroot00000000000000#include #include #include "fast_library.hpp" #include #include "log4cpp/Appender.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/Category.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include log4cpp::Category& logger = log4cpp::Category::getRoot(); /* 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); 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(convert_ip_as_string_to_uint_test, convert_ip_as_string_to_uint) { uint32_t ip = 0; convert_ip_as_string_to_uint_safe("255.255.255.0", ip); EXPECT_EQ(ip, convert_cidr_to_binary_netmask(24)); convert_ip_as_string_to_uint_safe("255.255.255.255", ip); EXPECT_EQ(ip, convert_cidr_to_binary_netmask(32)); } 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); 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_t 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"); } fastnetmon-1.2.8/src/fastnetmon_types.hpp000066400000000000000000000551011472727706000206200ustar00rootroot00000000000000#ifndef FASTNETMON_TYPES_H #define FASTNETMON_TYPES_H #ifdef _WIN32 #include #else #include // struct in6_addr #endif #include // uint32_t #include // struct timeval #include #include #include #include // std::pair #include #include "packet_storage.hpp" #include "fastnetmon_simple_packet.hpp" #include "subnet_counter.hpp" #include #include #include #include "fastnetmon_networks.hpp" enum attack_severity_t { ATTACK_SEVERITY_LOW, ATTACK_SEVERITY_MIDDLE, ATTACK_SEVERITY_HIGH }; // Attack action types enum class attack_action_t { ban, unban }; // Kafka traffic export formats enum class kafka_traffic_export_format_t : uint32_t { Unknown = 0, JSON = 1, Protobuf = 2 }; typedef std::vector vector_of_counters_t; typedef std::map configuration_map_t; typedef std::map graphite_data_t; // Enum with available sort by field enum sort_type_t { PACKETS, BYTES, FLOWS }; // Source of attack detection enum class attack_detection_source_t : uint32_t { Automatic = 1, Manual = 2, Other = 255 }; // Which direction of traffic triggered attack enum class attack_detection_direction_type_t { unknown, incoming, outgoing, }; // How we have detected this attack? enum class attack_detection_threshold_type_t { unknown, packets_per_second, bytes_per_second, flows_per_second, tcp_packets_per_second, udp_packets_per_second, icmp_packets_per_second, tcp_bytes_per_second, udp_bytes_per_second, icmp_bytes_per_second, tcp_syn_packets_per_second, tcp_syn_bytes_per_second, ip_fragments_packets_per_second, ip_fragments_bytes_per_second, }; // Types of metrics as in Prometheus: // https://prometheus.io/docs/concepts/metric_types/ enum class metric_type_t { counter, gauge }; // Here we store different counters class system_counter_t { public: system_counter_t(const std::string& counter_name, uint64_t counter_value, const metric_type_t& metric_type, const std::string& description) { this->counter_name = counter_name; this->counter_value = counter_value; this->counter_type = metric_type; this->counter_description = description; } std::string counter_name; uint64_t counter_value = 0; metric_type_t counter_type = metric_type_t::counter; std::string counter_description; }; /* Class for custom comparison fields by different fields */ template class TrafficComparatorClass { private: attack_detection_threshold_type_t sort_field; attack_detection_direction_type_t sort_direction; public: TrafficComparatorClass(attack_detection_direction_type_t sort_direction, attack_detection_threshold_type_t sort_field) { this->sort_field = sort_field; this->sort_direction = sort_direction; } bool operator()(T a, T b) { if (sort_field == attack_detection_threshold_type_t::flows_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.in_flows > b.second.in_flows; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.out_flows > b.second.out_flows; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::packets_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.total.in_packets > b.second.total.in_packets; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.total.out_packets > b.second.total.out_packets; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::bytes_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.total.in_bytes > b.second.total.in_bytes; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.total.out_bytes > b.second.total.out_bytes; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::tcp_packets_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.tcp.in_packets > b.second.tcp.in_packets; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.tcp.out_packets > b.second.tcp.out_packets; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::udp_packets_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.udp.in_packets > b.second.udp.in_packets; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.udp.out_packets > b.second.udp.out_packets; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::icmp_packets_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.icmp.in_packets > b.second.icmp.in_packets; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.icmp.out_packets > b.second.icmp.out_packets; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::tcp_bytes_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.tcp.in_bytes > b.second.tcp.in_bytes; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.tcp.out_bytes > b.second.tcp.out_bytes; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::udp_bytes_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.udp.in_bytes > b.second.udp.in_bytes; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.udp.out_bytes > b.second.udp.out_bytes; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::icmp_bytes_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.icmp.in_bytes > b.second.icmp.in_bytes; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.icmp.out_bytes > b.second.icmp.out_bytes; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::tcp_syn_packets_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.tcp_syn.in_packets > b.second.tcp_syn.in_packets; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.tcp_syn.out_packets > b.second.tcp_syn.out_packets; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::tcp_syn_bytes_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.tcp_syn.in_bytes > b.second.tcp_syn.in_bytes; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.tcp_syn.out_bytes > b.second.tcp_syn.out_bytes; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::ip_fragments_packets_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.fragmented.in_packets > b.second.fragmented.in_packets; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.fragmented.out_packets > b.second.fragmented.out_packets; } else { return false; } } else if (sort_field == attack_detection_threshold_type_t::ip_fragments_bytes_per_second) { if (sort_direction == attack_detection_direction_type_t::incoming) { return a.second.fragmented.in_bytes > b.second.fragmented.in_bytes; } else if (sort_direction == attack_detection_direction_type_t::outgoing) { return a.second.fragmented.out_bytes > b.second.fragmented.out_bytes; } else { return false; } } else { return false; } } }; 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; std::string logging_level = "info"; }; 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_t&); // 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, }; class single_counter_element_t { public: uint64_t bytes = 0; uint64_t packets = 0; uint64_t flows = 0; void zeroify() { bytes = 0; packets = 0; flows = 0; } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(bytes); ar& BOOST_SERIALIZATION_NVP(packets); ar& BOOST_SERIALIZATION_NVP(flows); } }; class total_counter_element_t { public: single_counter_element_t total{}; single_counter_element_t tcp; single_counter_element_t udp; single_counter_element_t icmp; single_counter_element_t fragmented; single_counter_element_t tcp_syn; single_counter_element_t dropped; void zeroify() { total.zeroify(); tcp.zeroify(); udp.zeroify(); icmp.zeroify(); fragmented.zeroify(); tcp_syn.zeroify(); dropped.zeroify(); } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(total); ar& BOOST_SERIALIZATION_NVP(tcp); ar& BOOST_SERIALIZATION_NVP(udp); ar& BOOST_SERIALIZATION_NVP(icmp); ar& BOOST_SERIALIZATION_NVP(fragmented); ar& BOOST_SERIALIZATION_NVP(tcp_syn); ar& BOOST_SERIALIZATION_NVP(dropped); } }; // Set of structures for calculating total traffic counters class total_speed_counters_t { public: total_counter_element_t total_counters[4]; total_counter_element_t total_speed_average_counters[4]; // Calculates speed void calculate_speed(double speed_calc_period, double average_calculation_time) { for (unsigned int index = 0; index < 4; index++) { total_counter_element_t total_speed_counters; // Calculate instant speed total_speed_counters.total.bytes = uint64_t((double)total_counters[index].total.bytes / (double)speed_calc_period); total_speed_counters.total.packets = uint64_t((double)total_counters[index].total.packets / (double)speed_calc_period); // tcp total_speed_counters.tcp.bytes = uint64_t((double)total_counters[index].tcp.bytes / (double)speed_calc_period); total_speed_counters.tcp.packets = uint64_t((double)total_counters[index].tcp.packets / (double)speed_calc_period); // udp total_speed_counters.udp.bytes = uint64_t((double)total_counters[index].udp.bytes / (double)speed_calc_period); total_speed_counters.udp.packets = uint64_t((double)total_counters[index].udp.packets / (double)speed_calc_period); // icmp total_speed_counters.icmp.bytes = uint64_t((double)total_counters[index].icmp.bytes / (double)speed_calc_period); total_speed_counters.icmp.packets = uint64_t((double)total_counters[index].icmp.packets / (double)speed_calc_period); // fragmented total_speed_counters.fragmented.bytes = uint64_t((double)total_counters[index].fragmented.bytes / (double)speed_calc_period); total_speed_counters.fragmented.packets = uint64_t((double)total_counters[index].fragmented.packets / (double)speed_calc_period); // tcp_syn total_speed_counters.tcp_syn.bytes = uint64_t((double)total_counters[index].tcp_syn.bytes / (double)speed_calc_period); total_speed_counters.tcp_syn.packets = uint64_t((double)total_counters[index].tcp_syn.packets / (double)speed_calc_period); // dropped total_speed_counters.dropped.bytes = uint64_t((double)total_counters[index].dropped.bytes / (double)speed_calc_period); total_speed_counters.dropped.packets = uint64_t((double)total_counters[index].dropped.packets / (double)speed_calc_period); // Calculate average speed double exp_power = -speed_calc_period / average_calculation_time; double exp_value = exp(exp_power); // Total total_speed_average_counters[index].total.bytes = uint64_t( total_speed_counters.total.bytes + exp_value * ((double)total_speed_average_counters[index].total.bytes - (double)total_speed_counters.total.bytes)); total_speed_average_counters[index].total.packets = uint64_t( total_speed_counters.total.packets + exp_value * ((double)total_speed_average_counters[index].total.packets - (double)total_speed_counters.total.packets)); // tcp total_speed_average_counters[index].tcp.bytes = uint64_t(total_speed_counters.tcp.bytes + exp_value * ((double)total_speed_average_counters[index].tcp.bytes - (double)total_speed_counters.tcp.bytes)); total_speed_average_counters[index].tcp.packets = uint64_t(total_speed_counters.tcp.packets + exp_value * ((double)total_speed_average_counters[index].tcp.packets - (double)total_speed_counters.tcp.packets)); // udp total_speed_average_counters[index].udp.bytes = uint64_t(total_speed_counters.udp.bytes + exp_value * ((double)total_speed_average_counters[index].udp.bytes - (double)total_speed_counters.udp.bytes)); total_speed_average_counters[index].udp.packets = uint64_t(total_speed_counters.udp.packets + exp_value * ((double)total_speed_average_counters[index].udp.packets - (double)total_speed_counters.udp.packets)); // icmp total_speed_average_counters[index].icmp.bytes = uint64_t(total_speed_counters.icmp.bytes + exp_value * ((double)total_speed_average_counters[index].icmp.bytes - (double)total_speed_counters.icmp.bytes)); total_speed_average_counters[index].icmp.packets = uint64_t( total_speed_counters.icmp.packets + exp_value * ((double)total_speed_average_counters[index].icmp.packets - (double)total_speed_counters.icmp.packets)); // fragmented total_speed_average_counters[index].fragmented.bytes = uint64_t(total_speed_counters.fragmented.bytes + exp_value * ((double)total_speed_average_counters[index].fragmented.bytes - (double)total_speed_counters.fragmented.bytes)); total_speed_average_counters[index].fragmented.packets = uint64_t(total_speed_counters.fragmented.packets + exp_value * ((double)total_speed_average_counters[index].fragmented.packets - (double)total_speed_counters.fragmented.packets)); // tcp_syn total_speed_average_counters[index].tcp_syn.bytes = uint64_t( total_speed_counters.tcp_syn.bytes + exp_value * ((double)total_speed_average_counters[index].tcp_syn.bytes - (double)total_speed_counters.tcp_syn.bytes)); total_speed_average_counters[index].tcp_syn.packets = uint64_t( total_speed_counters.tcp_syn.packets + exp_value * ((double)total_speed_average_counters[index].tcp_syn.packets - (double)total_speed_counters.tcp_syn.packets)); // dropped total_speed_average_counters[index].dropped.bytes = uint64_t( total_speed_counters.dropped.bytes + exp_value * ((double)total_speed_average_counters[index].dropped.bytes - (double)total_speed_counters.dropped.bytes)); total_speed_average_counters[index].dropped.packets = uint64_t( total_speed_counters.dropped.packets + exp_value * ((double)total_speed_average_counters[index].dropped.packets - (double)total_speed_counters.dropped.packets)); // nullify data counters after speed calculation total_counters[index].zeroify(); } } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(total_counters); ar& BOOST_SERIALIZATION_NVP(total_speed_average_counters); } }; // struct for save per direction and per protocol details for flow class conntrack_key_struct_t { public: uint64_t bytes = 0; uint64_t packets = 0; // will be used for Garbage Collection time_t last_update_time = 0; }; 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_t { 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_t; // Flow tracking structures typedef std::map map_of_vector_counters_for_flow_t; typedef subnet_counter_t subnet_counter_t; typedef std::pair pair_of_map_for_subnet_counters_elements_t; typedef std::map map_for_subnet_counters_t; // IPv6 per subnet counters typedef std::pair pair_of_map_for_ipv6_subnet_counters_elements_t; typedef std::unordered_map map_for_ipv6_subnet_counters_t; class packed_conntrack_hash_t { public: packed_conntrack_hash_t() : 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_ipv6(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_ipv6; 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.2.8/src/filter.cpp000066400000000000000000000225111472727706000164750ustar00rootroot00000000000000#include "filter.hpp" #include "iana_ip_protocols.hpp" #include "ip_lookup_tree.hpp" // Filter packet if it matches list of active flow spec announces bool filter_packet_by_flowspec_rule_list(const simple_packet_t& current_packet, const std::vector& active_flow_spec_announces) { for (auto& flow_announce : active_flow_spec_announces) { // Check that any rule matches specific flow spec announce if (filter_packet_by_flowspec_rule(current_packet, flow_announce)) { return true; } } return false; } // Returns true when packet matches specific rule bool filter_packet_by_flowspec_rule(const simple_packet_t& current_packet, const flow_spec_rule_t& flow_announce) { bool source_port_matches = false; bool destination_port_matches = false; bool source_ip_matches = false; bool destination_ip_matches = false; bool packet_size_matches = false; bool vlan_matches = false; bool tcp_flags_matches = false; bool fragmentation_flags_matches = false; bool protocol_matches = false; if (flow_announce.source_ports.size() == 0) { source_port_matches = true; } else { if (std::find(flow_announce.source_ports.begin(), flow_announce.source_ports.end(), current_packet.source_port) != flow_announce.source_ports.end()) { // We found this IP in list source_port_matches = true; } } if (flow_announce.destination_ports.size() == 0) { destination_port_matches = true; } else { if (std::find(flow_announce.destination_ports.begin(), flow_announce.destination_ports.end(), current_packet.destination_port) != flow_announce.destination_ports.end()) { // We found this IP in list destination_port_matches = true; } } if (flow_announce.protocols.size() == 0) { protocol_matches = true; } else { if (current_packet.protocol <= 255) { // Convert protocol as number into strictly typed protocol_type ip_protocol_t flow_protocol = get_ip_protocol_enum_type_from_integer(uint8_t(current_packet.protocol)); if (std::find(flow_announce.protocols.begin(), flow_announce.protocols.end(), flow_protocol) != flow_announce.protocols.end()) { protocol_matches = true; } } else { // Protocol cannot exceed 255! } } if (flow_announce.source_subnet_ipv4_used) { subnet_cidr_mask_t src_subnet; if (flow_announce.source_subnet_ipv4.cidr_prefix_length == 32) { src_subnet.set_cidr_prefix_length(32); src_subnet.set_subnet_address(current_packet.src_ip); if (flow_announce.source_subnet_ipv4 == src_subnet) { source_ip_matches = true; } } else { // We build Patricia tree for lookup but it's not very effective from performance perspective // To improve performance you may write native function to check if IP belongs to subnet_cidr_mask_t lookup_tree_32bit_t lookup_tree; lookup_tree.add_subnet(flow_announce.source_subnet_ipv4); if (lookup_tree.lookup_ip(current_packet.src_ip)) { source_ip_matches = true; } } } else if (flow_announce.source_subnet_ipv6_used) { if (flow_announce.source_subnet_ipv6.cidr_prefix_length == 128) { subnet_ipv6_cidr_mask_t src_subnet; src_subnet.set_cidr_prefix_length(128); src_subnet.set_subnet_address(¤t_packet.src_ipv6); if (flow_announce.source_subnet_ipv6 == src_subnet) { source_ip_matches = true; } } else { // We do not support non /128 boundaries yet } } else { source_ip_matches = true; } if (flow_announce.destination_subnet_ipv4_used) { if (flow_announce.destination_subnet_ipv4.cidr_prefix_length == 32) { subnet_cidr_mask_t dst_subnet; dst_subnet.set_cidr_prefix_length(32); dst_subnet.set_subnet_address(current_packet.dst_ip); if (flow_announce.destination_subnet_ipv4 == dst_subnet) { destination_ip_matches = true; } } else { // We build Patricia tree for lookup but it's not very effective from performance perspective // To improve performance you may write native function to check if IP belongs to subnet_cidr_mask_t lookup_tree_32bit_t lookup_tree; lookup_tree.add_subnet(flow_announce.destination_subnet_ipv4); if (lookup_tree.lookup_ip(current_packet.dst_ip)) { destination_ip_matches = true; } } } else if (flow_announce.destination_subnet_ipv6_used) { if (flow_announce.destination_subnet_ipv6.cidr_prefix_length == 128) { subnet_ipv6_cidr_mask_t dst_subnet; dst_subnet.set_cidr_prefix_length(128); dst_subnet.set_subnet_address(¤t_packet.dst_ipv6); if (flow_announce.destination_subnet_ipv6 == dst_subnet) { destination_ip_matches = true; } } else { // We do not support non /128 boundaries yet } } else { destination_ip_matches = true; } if (flow_announce.fragmentation_flags.size() == 0) { fragmentation_flags_matches = true; } else { for (auto& fragmentation_flag : flow_announce.fragmentation_flags) { // TODO: we are using only this two options in detection code but we should cover ALL possible cases! if (fragmentation_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_DONT_FRAGMENT) { if (current_packet.ip_dont_fragment) { fragmentation_flags_matches = true; } } else if (fragmentation_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_IS_A_FRAGMENT) { if (current_packet.ip_fragmented) { fragmentation_flags_matches = true; } } } } if (flow_announce.tcp_flags.size() == 0) { tcp_flags_matches = true; } else { // Convert 8bit representation of flags for this packet into flagset representation for flow spec flow_spec_tcp_flagset_t flagset; uint8t_representation_of_tcp_flags_to_flow_spec(current_packet.flags, flagset); // logger << log4cpp::Priority::WARN <<"Packet's tcp flagset: " << flagset.to_string(); if (std::find(flow_announce.tcp_flags.begin(), flow_announce.tcp_flags.end(), flagset) != flow_announce.tcp_flags.end()) { tcp_flags_matches = true; } } if (flow_announce.packet_lengths.size() == 0) { packet_size_matches = true; } else { // Use matching only if we can convert it to 16 bit value if (current_packet.length <= 65635) { // Convert 64 bit (we need it because netflow) value to 16 bit uint16_t packet_length_uint16 = (uint16_t)current_packet.length; // TODO: it's a bit incorrect to use check against packet length here // because as mentioned below we use total ip length for flow spec rules. // And here we have FULL packet length (including L2, Ethernet header) // But we just added ip_length field and need to check both if (std::find(flow_announce.packet_lengths.begin(), flow_announce.packet_lengths.end(), packet_length_uint16) != flow_announce.packet_lengths.end()) { packet_size_matches = true; } // Flow spec uses length in form "total length of IP packet" and we should check it uint16_t ip_packet_length_uint16 = (uint16_t)current_packet.ip_length; if (std::find(flow_announce.packet_lengths.begin(), flow_announce.packet_lengths.end(), ip_packet_length_uint16) != flow_announce.packet_lengths.end()) { packet_size_matches = true; } } } if (flow_announce.vlans.size() == 0) { vlan_matches = true; } else { if (std::find(flow_announce.vlans.begin(), flow_announce.vlans.end(), current_packet.vlan) != flow_announce.vlans.end()) { vlan_matches = true; } } // Nice thing for debug /* logger << log4cpp::Priority::WARN << "source_port_matches: " << source_port_matches << " " << "destination_port_matches: " << destination_port_matches << " " << "source_ip_matches: " << source_ip_matches << " " << "destination_ip_matches: " << destination_ip_matches << " " << "packet_size_matches: " << packet_size_matches << " " << "tcp_flags_matches: " << tcp_flags_matches << " " << "fragmentation_flags_matches: " << fragmentation_flags_matches << " " << "protocol_matches: " << protocol_matches; */ // Return true only of all parts matched if (source_port_matches && destination_port_matches && source_ip_matches && destination_ip_matches && packet_size_matches && tcp_flags_matches && fragmentation_flags_matches && protocol_matches && vlan_matches) { return true; } return false; } fastnetmon-1.2.8/src/filter.hpp000066400000000000000000000005751472727706000165100ustar00rootroot00000000000000#include "bgp_protocol_flow_spec.hpp" #include "fastnetmon_simple_packet.hpp" bool filter_packet_by_flowspec_rule_list(const simple_packet_t& current_packet, const std::vector& active_flow_spec_announces); bool filter_packet_by_flowspec_rule(const simple_packet_t& current_packet, const flow_spec_rule_t& flow_announce); fastnetmon-1.2.8/src/fixed_size_packet_storage.hpp000066400000000000000000000026441472727706000224260ustar00rootroot00000000000000#pragma once #include "fastnetmon_pcap_format.hpp" // We are using this class for storing packet meta information with their payload into fixed size memory region class fixed_size_packet_storage_t { public: fixed_size_packet_storage_t() = default; fixed_size_packet_storage_t(const void* payload_pointer, unsigned int captured_length, unsigned int real_packet_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); } packet_metadata.ts_sec = current_time.tv_sec; packet_metadata.ts_usec = current_time.tv_usec; // Store full length of packet packet_metadata.orig_len = real_packet_length; packet_metadata.incl_len = captured_length; // Copy only first 2048 bytes of data unsigned packet_length_for_storing = captured_length; if (captured_length > 2048) { packet_length_for_storing = 2048; } // Copy data into internal storage memcpy(packet_payload, payload_pointer, packet_length_for_storing); } // Some useful information about this packet fastnetmon_pcap_pkthdr_t packet_metadata; // Packet itself. Let's zeroify packet payload uint8_t packet_payload[2048] = {}; }; fastnetmon-1.2.8/src/fmt/000077500000000000000000000000001472727706000152715ustar00rootroot00000000000000fastnetmon-1.2.8/src/fmt/compile.h000066400000000000000000000520061472727706000170750ustar00rootroot00000000000000// Formatting library for C++ - experimental format string compilation // // Copyright (c) 2012 - present, Victor Zverovich and fmt contributors // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_COMPILE_H_ #define FMT_COMPILE_H_ #include "format.h" FMT_BEGIN_NAMESPACE namespace detail { // An output iterator that counts the number of objects written to it and // discards them. class counting_iterator { private: size_t count_; public: using iterator_category = std::output_iterator_tag; using difference_type = std::ptrdiff_t; using pointer = void; using reference = void; using _Unchecked_type = counting_iterator; // Mark iterator as checked. struct value_type { template void operator=(const T&) {} }; counting_iterator() : count_(0) {} size_t count() const { return count_; } counting_iterator& operator++() { ++count_; return *this; } counting_iterator operator++(int) { auto it = *this; ++*this; return it; } friend counting_iterator operator+(counting_iterator it, difference_type n) { it.count_ += static_cast(n); return it; } value_type operator*() const { return {}; } }; template inline counting_iterator copy_str(InputIt begin, InputIt end, counting_iterator it) { return it + (end - begin); } template class truncating_iterator_base { protected: OutputIt out_; size_t limit_; size_t count_ = 0; truncating_iterator_base() : out_(), limit_(0) {} truncating_iterator_base(OutputIt out, size_t limit) : out_(out), limit_(limit) {} public: using iterator_category = std::output_iterator_tag; using value_type = typename std::iterator_traits::value_type; using difference_type = std::ptrdiff_t; using pointer = void; using reference = void; using _Unchecked_type = truncating_iterator_base; // Mark iterator as checked. OutputIt base() const { return out_; } size_t count() const { return count_; } }; // An output iterator that truncates the output and counts the number of objects // written to it. template ::value_type>::type> class truncating_iterator; template class truncating_iterator : public truncating_iterator_base { mutable typename truncating_iterator_base::value_type blackhole_; public: using value_type = typename truncating_iterator_base::value_type; truncating_iterator() = default; truncating_iterator(OutputIt out, size_t limit) : truncating_iterator_base(out, limit) {} truncating_iterator& operator++() { if (this->count_++ < this->limit_) ++this->out_; return *this; } truncating_iterator operator++(int) { auto it = *this; ++*this; return it; } value_type& operator*() const { return this->count_ < this->limit_ ? *this->out_ : blackhole_; } }; template class truncating_iterator : public truncating_iterator_base { public: truncating_iterator() = default; truncating_iterator(OutputIt out, size_t limit) : truncating_iterator_base(out, limit) {} template truncating_iterator& operator=(T val) { if (this->count_++ < this->limit_) *this->out_++ = val; return *this; } truncating_iterator& operator++() { return *this; } truncating_iterator& operator++(int) { return *this; } truncating_iterator& operator*() { return *this; } }; // A compile-time string which is compiled into fast formatting code. class compiled_string {}; template struct is_compiled_string : std::is_base_of {}; /** \rst Converts a string literal *s* into a format string that will be parsed at compile time and converted into efficient formatting code. Requires C++17 ``constexpr if`` compiler support. **Example**:: // Converts 42 into std::string using the most efficient method and no // runtime format string processing. std::string s = fmt::format(FMT_COMPILE("{}"), 42); \endrst */ #ifdef __cpp_if_constexpr # define FMT_COMPILE(s) \ FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit) #else # define FMT_COMPILE(s) FMT_STRING(s) #endif #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS template Str> struct udl_compiled_string : compiled_string { using char_type = Char; constexpr operator basic_string_view() const { return {Str.data, N - 1}; } }; #endif template const T& first(const T& value, const Tail&...) { return value; } #ifdef __cpp_if_constexpr template struct type_list {}; // Returns a reference to the argument at index N from [first, rest...]. template constexpr const auto& get([[maybe_unused]] const T& first, [[maybe_unused]] const Args&... rest) { static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); if constexpr (N == 0) return first; else return get(rest...); } template constexpr int get_arg_index_by_name(basic_string_view name, type_list) { return get_arg_index_by_name(name); } template struct get_type_impl; template struct get_type_impl> { using type = remove_cvref_t(std::declval()...))>; }; template using get_type = typename get_type_impl::type; template struct is_compiled_format : std::false_type {}; template struct text { basic_string_view data; using char_type = Char; template constexpr OutputIt format(OutputIt out, const Args&...) const { return write(out, data); } }; template struct is_compiled_format> : std::true_type {}; template constexpr text make_text(basic_string_view s, size_t pos, size_t size) { return {{&s[pos], size}}; } template struct code_unit { Char value; using char_type = Char; template constexpr OutputIt format(OutputIt out, const Args&...) const { return write(out, value); } }; // This ensures that the argument type is convertible to `const T&`. template constexpr const T& get_arg_checked(const Args&... args) { const auto& arg = get(args...); if constexpr (detail::is_named_arg>()) { return arg.value; } else { return arg; } } template struct is_compiled_format> : std::true_type {}; // A replacement field that refers to argument N. template struct field { using char_type = Char; template constexpr OutputIt format(OutputIt out, const Args&... args) const { return write(out, get_arg_checked(args...)); } }; template struct is_compiled_format> : std::true_type {}; // A replacement field that refers to argument with name. template struct runtime_named_field { using char_type = Char; basic_string_view name; template constexpr static bool try_format_argument( OutputIt& out, // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 [[maybe_unused]] basic_string_view arg_name, const T& arg) { if constexpr (is_named_arg::type>::value) { if (arg_name == arg.name) { out = write(out, arg.value); return true; } } return false; } template constexpr OutputIt format(OutputIt out, const Args&... args) const { bool found = (try_format_argument(out, name, args) || ...); if (!found) { throw format_error("argument with specified name is not found"); } return out; } }; template struct is_compiled_format> : std::true_type {}; // A replacement field that refers to argument N and has format specifiers. template struct spec_field { using char_type = Char; formatter fmt; template constexpr FMT_INLINE OutputIt format(OutputIt out, const Args&... args) const { const auto& vargs = fmt::make_format_args>(args...); basic_format_context ctx(out, vargs); return fmt.format(get_arg_checked(args...), ctx); } }; template struct is_compiled_format> : std::true_type {}; template struct concat { L lhs; R rhs; using char_type = typename L::char_type; template constexpr OutputIt format(OutputIt out, const Args&... args) const { out = lhs.format(out, args...); return rhs.format(out, args...); } }; template struct is_compiled_format> : std::true_type {}; template constexpr concat make_concat(L lhs, R rhs) { return {lhs, rhs}; } struct unknown_format {}; template constexpr size_t parse_text(basic_string_view str, size_t pos) { for (size_t size = str.size(); pos != size; ++pos) { if (str[pos] == '{' || str[pos] == '}') break; } return pos; } template constexpr auto compile_format_string(S format_str); template constexpr auto parse_tail(T head, S format_str) { if constexpr (POS != basic_string_view(format_str).size()) { constexpr auto tail = compile_format_string(format_str); if constexpr (std::is_same, unknown_format>()) return tail; else return make_concat(head, tail); } else { return head; } } template struct parse_specs_result { formatter fmt; size_t end; int next_arg_id; }; constexpr int manual_indexing_id = -1; template constexpr parse_specs_result parse_specs(basic_string_view str, size_t pos, int next_arg_id) { str.remove_prefix(pos); auto ctx = basic_format_parse_context(str, {}, next_arg_id); auto f = formatter(); auto end = f.parse(ctx); return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1, next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; } template struct arg_id_handler { arg_ref arg_id; constexpr int operator()() { FMT_ASSERT(false, "handler cannot be used with automatic indexing"); return 0; } constexpr int operator()(int id) { arg_id = arg_ref(id); return 0; } constexpr int operator()(basic_string_view id) { arg_id = arg_ref(id); return 0; } constexpr void on_error(const char* message) { throw format_error(message); } }; template struct parse_arg_id_result { arg_ref arg_id; const Char* arg_id_end; }; template constexpr auto parse_arg_id(const Char* begin, const Char* end) { auto handler = arg_id_handler{arg_ref{}}; auto arg_id_end = parse_arg_id(begin, end, handler); return parse_arg_id_result{handler.arg_id, arg_id_end}; } template struct field_type { using type = remove_cvref_t; }; template struct field_type::value>> { using type = remove_cvref_t; }; template constexpr auto parse_replacement_field_then_tail(S format_str) { using char_type = typename S::char_type; constexpr auto str = basic_string_view(format_str); constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); if constexpr (c == '}') { return parse_tail( field::type, ARG_INDEX>(), format_str); } else if constexpr (c == ':') { constexpr auto result = parse_specs::type>( str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); return parse_tail( spec_field::type, ARG_INDEX>{ result.fmt}, format_str); } } // Compiles a non-empty format string and returns the compiled representation // or unknown_format() on unrecognized input. template constexpr auto compile_format_string(S format_str) { using char_type = typename S::char_type; constexpr auto str = basic_string_view(format_str); if constexpr (str[POS] == '{') { if constexpr (POS + 1 == str.size()) throw format_error("unmatched '{' in format string"); if constexpr (str[POS + 1] == '{') { return parse_tail(make_text(str, POS, 1), format_str); } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { static_assert(ID != manual_indexing_id, "cannot switch from manual to automatic argument indexing"); constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail, Args, POS + 1, ID, next_id>( format_str); } else { constexpr auto arg_id_result = parse_arg_id(str.data() + POS + 1, str.data() + str.size()); constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); constexpr char_type c = arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); static_assert(c == '}' || c == ':', "missing '}' in format string"); if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { static_assert( ID == manual_indexing_id || ID == 0, "cannot switch from automatic to manual argument indexing"); constexpr auto arg_index = arg_id_result.arg_id.val.index; return parse_replacement_field_then_tail, Args, arg_id_end_pos, arg_index, manual_indexing_id>( format_str); } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { constexpr auto arg_index = get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); if constexpr (arg_index != invalid_arg_index) { constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail< decltype(get_type::value), Args, arg_id_end_pos, arg_index, next_id>(format_str); } else { if constexpr (c == '}') { return parse_tail( runtime_named_field{arg_id_result.arg_id.val.name}, format_str); } else if constexpr (c == ':') { return unknown_format(); // no type info for specs parsing } } } } } else if constexpr (str[POS] == '}') { if constexpr (POS + 1 == str.size()) throw format_error("unmatched '}' in format string"); return parse_tail(make_text(str, POS, 1), format_str); } else { constexpr auto end = parse_text(str, POS + 1); if constexpr (end - POS > 1) { return parse_tail(make_text(str, POS, end - POS), format_str); } else { return parse_tail(code_unit{str[POS]}, format_str); } } } template ::value)> constexpr auto compile(S format_str) { constexpr auto str = basic_string_view(format_str); if constexpr (str.size() == 0) { return detail::make_text(str, 0, 0); } else { constexpr auto result = detail::compile_format_string, 0, 0>( format_str); return result; } } #endif // __cpp_if_constexpr } // namespace detail FMT_MODULE_EXPORT_BEGIN #ifdef __cpp_if_constexpr template ::value)> FMT_INLINE std::basic_string format(const CompiledFormat& cf, const Args&... args) { auto s = std::basic_string(); cf.format(std::back_inserter(s), args...); return s; } template ::value)> constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) { return cf.format(out, args...); } template ::value)> FMT_INLINE std::basic_string format(const S&, Args&&... args) { if constexpr (std::is_same::value) { constexpr auto str = basic_string_view(S()); if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { const auto& first = detail::first(args...); if constexpr (detail::is_named_arg< remove_cvref_t>::value) { return fmt::to_string(first.value); } else { return fmt::to_string(first); } } } constexpr auto compiled = detail::compile(S()); if constexpr (std::is_same, detail::unknown_format>()) { return format(static_cast>(S()), std::forward(args)...); } else { return format(compiled, std::forward(args)...); } } template ::value)> FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { constexpr auto compiled = detail::compile(S()); if constexpr (std::is_same, detail::unknown_format>()) { return format_to(out, static_cast>(S()), std::forward(args)...); } else { return format_to(out, compiled, std::forward(args)...); } } #endif template ::value)> format_to_n_result format_to_n(OutputIt out, size_t n, const S& format_str, Args&&... args) { auto it = format_to(detail::truncating_iterator(out, n), format_str, std::forward(args)...); return {it.base(), it.count()}; } template ::value)> size_t formatted_size(const S& format_str, const Args&... args) { return format_to(detail::counting_iterator(), format_str, args...).count(); } template ::value)> void print(std::FILE* f, const S& format_str, const Args&... args) { memory_buffer buffer; format_to(std::back_inserter(buffer), format_str, args...); detail::print(f, {buffer.data(), buffer.size()}); } template ::value)> void print(const S& format_str, const Args&... args) { print(stdout, format_str, args...); } #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS inline namespace literals { template constexpr detail::udl_compiled_string< remove_cvref_t, sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str> operator""_cf() { return {}; } } // namespace literals #endif FMT_MODULE_EXPORT_END FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ fastnetmon-1.2.8/src/fmt/core.h000066400000000000000000002777021472727706000164110ustar00rootroot00000000000000// Formatting library for C++ - the core API for char/UTF-8 // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_CORE_H_ #define FMT_CORE_H_ #include // std::FILE #include #include #include #include #include // The fmt library version in the form major * 10000 + minor * 100 + patch. #define FMT_VERSION 80000 #ifdef __clang__ # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) #else # define FMT_CLANG_VERSION 0 #endif #if defined(__GNUC__) && !defined(__clang__) # define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) # define FMT_GCC_PRAGMA(arg) _Pragma(arg) #else # define FMT_GCC_VERSION 0 # define FMT_GCC_PRAGMA(arg) #endif #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) # define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION #else # define FMT_HAS_GXX_CXX11 0 #endif #if defined(__INTEL_COMPILER) # define FMT_ICC_VERSION __INTEL_COMPILER #else # define FMT_ICC_VERSION 0 #endif #ifdef __NVCC__ # define FMT_NVCC __NVCC__ #else # define FMT_NVCC 0 #endif #ifdef _MSC_VER # define FMT_MSC_VER _MSC_VER # define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) #else # define FMT_MSC_VER 0 # define FMT_MSC_WARNING(...) #endif #ifdef __has_feature # define FMT_HAS_FEATURE(x) __has_feature(x) #else # define FMT_HAS_FEATURE(x) 0 #endif #if defined(__has_include) && !defined(__INTELLISENSE__) && \ (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600) # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 #endif #ifdef __has_cpp_attribute # define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) #else # define FMT_HAS_CPP_ATTRIBUTE(x) 0 #endif #define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) #define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) // Check if relaxed C++14 constexpr is supported. // GCC doesn't allow throw in constexpr until version 6 (bug 67371). #ifndef FMT_USE_CONSTEXPR # define FMT_USE_CONSTEXPR \ (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \ !FMT_NVCC && !FMT_ICC_VERSION #endif #if FMT_USE_CONSTEXPR # define FMT_CONSTEXPR constexpr # define FMT_CONSTEXPR_DECL constexpr #else # define FMT_CONSTEXPR # define FMT_CONSTEXPR_DECL #endif // Check if constexpr std::char_traits<>::compare,length is supported. #if defined(__GLIBCXX__) # if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \ _GLIBCXX_RELEASE >= 7 // GCC 7+ libstdc++ has _GLIBCXX_RELEASE. # define FMT_CONSTEXPR_CHAR_TRAITS constexpr # endif #elif defined(_LIBCPP_VERSION) && __cplusplus >= 201703L && \ _LIBCPP_VERSION >= 4000 # define FMT_CONSTEXPR_CHAR_TRAITS constexpr #elif FMT_MSC_VER >= 1914 && _MSVC_LANG >= 201703L # define FMT_CONSTEXPR_CHAR_TRAITS constexpr #endif #ifndef FMT_CONSTEXPR_CHAR_TRAITS # define FMT_CONSTEXPR_CHAR_TRAITS #endif #ifndef FMT_OVERRIDE # if FMT_HAS_FEATURE(cxx_override_control) || \ (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 # define FMT_OVERRIDE override # else # define FMT_OVERRIDE # endif #endif // Check if exceptions are disabled. #ifndef FMT_EXCEPTIONS # if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ FMT_MSC_VER && !_HAS_EXCEPTIONS # define FMT_EXCEPTIONS 0 # else # define FMT_EXCEPTIONS 1 # endif #endif // Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). #ifndef FMT_USE_NOEXCEPT # define FMT_USE_NOEXCEPT 0 #endif #if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 # define FMT_DETECTED_NOEXCEPT noexcept # define FMT_HAS_CXX11_NOEXCEPT 1 #else # define FMT_DETECTED_NOEXCEPT throw() # define FMT_HAS_CXX11_NOEXCEPT 0 #endif #ifndef FMT_NOEXCEPT # if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT # define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT # else # define FMT_NOEXCEPT # endif #endif // [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code // warnings. #if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VER && \ !FMT_NVCC # define FMT_NORETURN [[noreturn]] #else # define FMT_NORETURN #endif #ifndef FMT_MAYBE_UNUSED # if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) # define FMT_MAYBE_UNUSED [[maybe_unused]] # else # define FMT_MAYBE_UNUSED # endif #endif #if __cplusplus == 201103L || __cplusplus == 201402L # if defined(__INTEL_COMPILER) || defined(__PGI) # define FMT_FALLTHROUGH # elif defined(__clang__) # define FMT_FALLTHROUGH [[clang::fallthrough]] # elif FMT_GCC_VERSION >= 700 && \ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) # define FMT_FALLTHROUGH [[gnu::fallthrough]] # else # define FMT_FALLTHROUGH # endif #elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \ (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define FMT_FALLTHROUGH [[fallthrough]] #else # define FMT_FALLTHROUGH #endif #ifndef FMT_USE_FLOAT # define FMT_USE_FLOAT 1 #endif #ifndef FMT_USE_DOUBLE # define FMT_USE_DOUBLE 1 #endif #ifndef FMT_USE_LONG_DOUBLE # define FMT_USE_LONG_DOUBLE 1 #endif #ifndef FMT_INLINE # if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_INLINE inline __attribute__((always_inline)) # else # define FMT_INLINE inline # endif #endif #ifndef FMT_USE_INLINE_NAMESPACES # if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ (FMT_MSC_VER >= 1900 && (!defined(_MANAGED) || !_MANAGED)) # define FMT_USE_INLINE_NAMESPACES 1 # else # define FMT_USE_INLINE_NAMESPACES 0 # endif #endif #ifndef FMT_BEGIN_NAMESPACE # if FMT_USE_INLINE_NAMESPACES # define FMT_INLINE_NAMESPACE inline namespace # define FMT_END_NAMESPACE \ } \ } # else # define FMT_INLINE_NAMESPACE namespace # define FMT_END_NAMESPACE \ } \ using namespace v7; \ } # endif # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ FMT_INLINE_NAMESPACE v7 { #endif #ifndef FMT_MODULE_EXPORT # define FMT_MODULE_EXPORT # define FMT_MODULE_EXPORT_BEGIN # define FMT_MODULE_EXPORT_END # define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { # define FMT_END_DETAIL_NAMESPACE } #endif #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) # define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) # ifdef FMT_EXPORT # define FMT_API __declspec(dllexport) # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # endif #else # define FMT_CLASS_API # if defined(FMT_EXPORT) || defined(FMT_SHARED) # if defined(__GNUC__) || defined(__clang__) # define FMT_API __attribute__((visibility("default"))) # endif # endif #endif #ifndef FMT_API # define FMT_API #endif #if FMT_GCC_VERSION # define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) #else # define FMT_GCC_VISIBILITY_HIDDEN #endif // libc++ supports string_view in pre-c++17. #if (FMT_HAS_INCLUDE() && \ (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) # include # define FMT_USE_STRING_VIEW #elif FMT_HAS_INCLUDE("experimental/string_view") && __cplusplus >= 201402L # include # define FMT_USE_EXPERIMENTAL_STRING_VIEW #endif #ifndef FMT_UNICODE # define FMT_UNICODE !FMT_MSC_VER #endif #ifndef FMT_CONSTEVAL # if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ __cplusplus > 201703L) || \ (defined(__cpp_consteval) && \ !FMT_MSC_VER) // consteval is broken in MSVC. # define FMT_CONSTEVAL consteval # define FMT_HAS_CONSTEVAL # else # define FMT_CONSTEVAL # endif #endif #ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS # if defined(__cpp_nontype_template_args) && \ ((FMT_GCC_VERSION >= 903 && __cplusplus >= 201709L) || \ __cpp_nontype_template_args >= 201911L) # define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 1 # else # define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0 # endif #endif // Enable minimal optimizations for more compact code in debug mode. FMT_GCC_PRAGMA("GCC push_options") #ifndef __OPTIMIZE__ FMT_GCC_PRAGMA("GCC optimize(\"Og\")") #endif FMT_BEGIN_NAMESPACE FMT_MODULE_EXPORT_BEGIN // Implementations of enable_if_t and other metafunctions for older systems. template using enable_if_t = typename std::enable_if::type; template using conditional_t = typename std::conditional::type; template using bool_constant = std::integral_constant; template using remove_reference_t = typename std::remove_reference::type; template using remove_cvref_t = typename std::remove_cv>::type; template struct type_identity { using type = T; }; template using type_identity_t = typename type_identity::type; struct monostate { constexpr monostate() {} }; // An enable_if helper to be used in template parameters which results in much // shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed // to workaround a bug in MSVC 2019 (see #1140 and #1186). #ifdef FMT_DOC # define FMT_ENABLE_IF(...) #else # define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 #endif FMT_BEGIN_DETAIL_NAMESPACE constexpr FMT_INLINE auto is_constant_evaluated() FMT_NOEXCEPT -> bool { #ifdef __cpp_lib_is_constant_evaluated return std::is_constant_evaluated(); #else return false; #endif } // A function to suppress "conditional expression is constant" warnings. template constexpr auto const_check(T value) -> T { return value; } FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); #ifndef FMT_ASSERT # ifdef NDEBUG // FMT_ASSERT is not empty to avoid -Werror=empty-body. # define FMT_ASSERT(condition, message) ((void)0) # else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ? (void)0 \ : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) # endif #endif #if defined(FMT_USE_STRING_VIEW) template using std_string_view = std::basic_string_view; #elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) template using std_string_view = std::experimental::basic_string_view; #else template struct std_string_view {}; #endif #ifdef FMT_USE_INT128 // Do nothing. #elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \ !(FMT_CLANG_VERSION && FMT_MSC_VER) # define FMT_USE_INT128 1 using int128_t = __int128_t; using uint128_t = __uint128_t; template inline auto convert_for_visit(T value) -> T { return value; } #else # define FMT_USE_INT128 0 #endif #if !FMT_USE_INT128 enum class int128_t {}; enum class uint128_t {}; // Reduce template instantiations. template inline auto convert_for_visit(T) -> monostate { return {}; } #endif // Casts a nonnegative integer to unsigned. template FMT_CONSTEXPR auto to_unsigned(Int value) -> typename std::make_unsigned::type { FMT_ASSERT(value >= 0, "negative value"); return static_cast::type>(value); } FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; constexpr auto is_utf8() -> bool { // Avoid buggy sign extensions in MSVC's constant evaluation mode. // https://developercommunity.visualstudio.com/t/C-difference-in-behavior-for-unsigned/1233612 using uchar = unsigned char; return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 && uchar(micro[1]) == 0xB5); } FMT_END_DETAIL_NAMESPACE /** An implementation of ``std::basic_string_view`` for pre-C++17. It provides a subset of the API. ``fmt::basic_string_view`` is used for format strings even if ``std::string_view`` is available to prevent issues when a library is compiled with a different ``-std`` option than the client code (which is not recommended). */ template class basic_string_view { private: const Char* data_; size_t size_; public: using value_type = Char; using iterator = const Char*; constexpr basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {} /** Constructs a string reference object from a C string and a size. */ constexpr basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT : data_(s), size_(count) {} /** \rst Constructs a string reference object from a C string computing the size with ``std::char_traits::length``. \endrst */ FMT_CONSTEXPR_CHAR_TRAITS FMT_INLINE basic_string_view(const Char* s) : data_(s) { if (detail::const_check(std::is_same::value && !detail::is_constant_evaluated())) size_ = std::strlen(reinterpret_cast(s)); else size_ = std::char_traits::length(s); } /** Constructs a string reference from a ``std::basic_string`` object. */ template FMT_CONSTEXPR basic_string_view( const std::basic_string& s) FMT_NOEXCEPT : data_(s.data()), size_(s.size()) {} template >::value)> FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()), size_(s.size()) {} /** Returns a pointer to the string data. */ constexpr auto data() const -> const Char* { return data_; } /** Returns the string size. */ constexpr auto size() const -> size_t { return size_; } constexpr auto begin() const -> iterator { return data_; } constexpr auto end() const -> iterator { return data_ + size_; } constexpr auto operator[](size_t pos) const -> const Char& { return data_[pos]; } FMT_CONSTEXPR void remove_prefix(size_t n) { data_ += n; size_ -= n; } // Lexicographically compare this string reference to other. FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { size_t str_size = size_ < other.size_ ? size_ : other.size_; int result = std::char_traits::compare(data_, other.data_, str_size); if (result == 0) result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); return result; } FMT_CONSTEXPR_CHAR_TRAITS friend auto operator==(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) == 0; } friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) != 0; } friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) < 0; } friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) <= 0; } friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) > 0; } friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) >= 0; } }; using string_view = basic_string_view; /** Specifies if ``T`` is a character type. Can be specialized by users. */ template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; /** \rst Returns a string view of `s`. In order to add custom string type support to {fmt} provide an overload of `to_string_view` for it in the same namespace as the type for the argument-dependent lookup to work. **Example**:: namespace my_ns { inline string_view to_string_view(const my_string& s) { return {s.data(), s.length()}; } } std::string message = fmt::format(my_string("The answer is {}"), 42); \endrst */ template ::value)> FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view { return s; } template inline auto to_string_view(const std::basic_string& s) -> basic_string_view { return s; } template constexpr auto to_string_view(basic_string_view s) -> basic_string_view { return s; } template >::value)> inline auto to_string_view(detail::std_string_view s) -> basic_string_view { return s; } // A base class for compile-time strings. It is defined in the fmt namespace to // make formatting functions visible via ADL, e.g. format(FMT_STRING("{}"), 42). struct compile_string {}; template struct is_compile_string : std::is_base_of {}; template ::value)> constexpr auto to_string_view(const S& s) -> basic_string_view { return basic_string_view(s); } FMT_BEGIN_DETAIL_NAMESPACE void to_string_view(...); using fmt::v7::to_string_view; // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in // enable_if and MSVC 2015 fails to compile it as an alias template. template struct is_string : std::is_class()))> { }; template struct char_t_impl {}; template struct char_t_impl::value>> { using result = decltype(to_string_view(std::declval())); using type = typename result::value_type; }; // Reports a compile-time error if S is not a valid format string. template ::value)> FMT_INLINE void check_format_string(const S&) { #ifdef FMT_ENFORCE_COMPILE_STRING static_assert(is_compile_string::value, "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " "FMT_STRING."); #endif } template ::value)> void check_format_string(S); struct error_handler { constexpr error_handler() = default; constexpr error_handler(const error_handler&) = default; // This function is intentionally not constexpr to give a compile-time error. FMT_NORETURN FMT_API void on_error(const char* message); }; FMT_END_DETAIL_NAMESPACE /** String's character type. */ template using char_t = typename detail::char_t_impl::type; /** \rst Parsing context consisting of a format string range being parsed and an argument counter for automatic indexing. You can use the ```format_parse_context`` type alias for ``char`` instead. \endrst */ template class basic_format_parse_context : private ErrorHandler { private: basic_string_view format_str_; int next_arg_id_; public: using char_type = Char; using iterator = typename basic_string_view::iterator; explicit constexpr basic_format_parse_context( basic_string_view format_str, ErrorHandler eh = {}, int next_arg_id = 0) : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {} /** Returns an iterator to the beginning of the format string range being parsed. */ constexpr auto begin() const FMT_NOEXCEPT -> iterator { return format_str_.begin(); } /** Returns an iterator past the end of the format string range being parsed. */ constexpr auto end() const FMT_NOEXCEPT -> iterator { return format_str_.end(); } /** Advances the begin iterator to ``it``. */ FMT_CONSTEXPR void advance_to(iterator it) { format_str_.remove_prefix(detail::to_unsigned(it - begin())); } /** Reports an error if using the manual argument indexing; otherwise returns the next argument index and switches to the automatic indexing. */ FMT_CONSTEXPR auto next_arg_id() -> int { // Don't check if the argument id is valid to avoid overhead and because it // will be checked during formatting anyway. if (next_arg_id_ >= 0) return next_arg_id_++; on_error("cannot switch from manual to automatic argument indexing"); return 0; } /** Reports an error if using the automatic argument indexing; otherwise switches to the manual indexing. */ FMT_CONSTEXPR void check_arg_id(int) { if (next_arg_id_ > 0) on_error("cannot switch from automatic to manual argument indexing"); else next_arg_id_ = -1; } FMT_CONSTEXPR void check_arg_id(basic_string_view) {} FMT_CONSTEXPR void on_error(const char* message) { ErrorHandler::on_error(message); } constexpr auto error_handler() const -> ErrorHandler { return *this; } }; using format_parse_context = basic_format_parse_context; template class basic_format_arg; template class basic_format_args; template class dynamic_format_arg_store; // A formatter for objects of type T. template struct formatter { // A deleted default constructor indicates a disabled formatter. formatter() = delete; }; // Specifies if T has an enabled formatter specialization. A type can be // formattable even if it doesn't have a formatter e.g. via a conversion. template using has_formatter = std::is_constructible>; // Checks whether T is a container with contiguous storage. template struct is_contiguous : std::false_type {}; template struct is_contiguous> : std::true_type {}; class appender; FMT_BEGIN_DETAIL_NAMESPACE // Extracts a reference to the container from back_insert_iterator. template inline auto get_container(std::back_insert_iterator it) -> Container& { using bi_iterator = std::back_insert_iterator; struct accessor : bi_iterator { accessor(bi_iterator iter) : bi_iterator(iter) {} using bi_iterator::container; }; return *accessor(it).container; } template FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out) -> OutputIt { while (begin != end) *out++ = static_cast(*begin++); return out; } template ::value)> FMT_CONSTEXPR auto copy_str(const Char* begin, const Char* end, Char* out) -> Char* { if (is_constant_evaluated()) return copy_str(begin, end, out); auto size = to_unsigned(end - begin); memcpy(out, begin, size); return out + size; } /** \rst A contiguous memory buffer with an optional growing ability. It is an internal class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`. \endrst */ template class buffer { private: T* ptr_; size_t size_; size_t capacity_; protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. FMT_MSC_WARNING(suppress : 26495) buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT : ptr_(p), size_(sz), capacity_(cap) {} ~buffer() = default; buffer(buffer&&) = default; /** Sets the buffer data and capacity. */ void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { ptr_ = buf_data; capacity_ = buf_capacity; } /** Increases the buffer capacity to hold at least *capacity* elements. */ virtual void grow(size_t capacity) = 0; public: using value_type = T; using const_reference = const T&; buffer(const buffer&) = delete; void operator=(const buffer&) = delete; auto begin() FMT_NOEXCEPT -> T* { return ptr_; } auto end() FMT_NOEXCEPT -> T* { return ptr_ + size_; } auto begin() const FMT_NOEXCEPT -> const T* { return ptr_; } auto end() const FMT_NOEXCEPT -> const T* { return ptr_ + size_; } /** Returns the size of this buffer. */ auto size() const FMT_NOEXCEPT -> size_t { return size_; } /** Returns the capacity of this buffer. */ auto capacity() const FMT_NOEXCEPT -> size_t { return capacity_; } /** Returns a pointer to the buffer data. */ auto data() FMT_NOEXCEPT -> T* { return ptr_; } /** Returns a pointer to the buffer data. */ auto data() const FMT_NOEXCEPT -> const T* { return ptr_; } /** Clears this buffer. */ void clear() { size_ = 0; } // Tries resizing the buffer to contain *count* elements. If T is a POD type // the new elements may not be initialized. void try_resize(size_t count) { try_reserve(count); size_ = count <= capacity_ ? count : capacity_; } // Tries increasing the buffer capacity to *new_capacity*. It can increase the // capacity by a smaller amount than requested but guarantees there is space // for at least one additional element either by increasing the capacity or by // flushing the buffer if it is full. void try_reserve(size_t new_capacity) { if (new_capacity > capacity_) grow(new_capacity); } void push_back(const T& value) { try_reserve(size_ + 1); ptr_[size_++] = value; } /** Appends data to the end of the buffer. */ template void append(const U* begin, const U* end); template auto operator[](I index) -> T& { return ptr_[index]; } template auto operator[](I index) const -> const T& { return ptr_[index]; } }; struct buffer_traits { explicit buffer_traits(size_t) {} auto count() const -> size_t { return 0; } auto limit(size_t size) -> size_t { return size; } }; class fixed_buffer_traits { private: size_t count_ = 0; size_t limit_; public: explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} auto count() const -> size_t { return count_; } auto limit(size_t size) -> size_t { size_t n = limit_ > count_ ? limit_ - count_ : 0; count_ += size; return size < n ? size : n; } }; // A buffer that writes to an output iterator when flushed. template class iterator_buffer final : public Traits, public buffer { private: OutputIt out_; enum { buffer_size = 256 }; T data_[buffer_size]; protected: void grow(size_t) final FMT_OVERRIDE { if (this->size() == buffer_size) flush(); } void flush() { auto size = this->size(); this->clear(); out_ = copy_str(data_, data_ + this->limit(size), out_); } public: explicit iterator_buffer(OutputIt out, size_t n = buffer_size) : Traits(n), buffer(data_, 0, buffer_size), out_(out) {} iterator_buffer(iterator_buffer&& other) : Traits(other), buffer(data_, 0, buffer_size), out_(other.out_) {} ~iterator_buffer() { flush(); } auto out() -> OutputIt { flush(); return out_; } auto count() const -> size_t { return Traits::count() + this->size(); } }; template class iterator_buffer final : public buffer { protected: void grow(size_t) final FMT_OVERRIDE {} public: explicit iterator_buffer(T* out, size_t = 0) : buffer(out, 0, ~size_t()) {} auto out() -> T* { return &*this->end(); } }; // A buffer that writes to a container with the contiguous storage. template class iterator_buffer, enable_if_t::value, typename Container::value_type>> final : public buffer { private: Container& container_; protected: void grow(size_t capacity) final FMT_OVERRIDE { container_.resize(capacity); this->set(&container_[0], capacity); } public: explicit iterator_buffer(Container& c) : buffer(c.size()), container_(c) {} explicit iterator_buffer(std::back_insert_iterator out, size_t = 0) : iterator_buffer(get_container(out)) {} auto out() -> std::back_insert_iterator { return std::back_inserter(container_); } }; // A buffer that counts the number of code units written discarding the output. template class counting_buffer final : public buffer { private: enum { buffer_size = 256 }; T data_[buffer_size]; size_t count_ = 0; protected: void grow(size_t) final FMT_OVERRIDE { if (this->size() != buffer_size) return; count_ += this->size(); this->clear(); } public: counting_buffer() : buffer(data_, 0, buffer_size) {} auto count() -> size_t { return count_ + this->size(); } }; template using buffer_appender = conditional_t::value, appender, std::back_insert_iterator>>; // Maps an output iterator to a buffer. template auto get_buffer(OutputIt out) -> iterator_buffer { return iterator_buffer(out); } template auto get_iterator(Buffer& buf) -> decltype(buf.out()) { return buf.out(); } template auto get_iterator(buffer& buf) -> buffer_appender { return buffer_appender(buf); } template struct fallback_formatter { fallback_formatter() = delete; }; // Specifies if T has an enabled fallback_formatter specialization. template using has_fallback_formatter = std::is_constructible>; struct view {}; template struct named_arg : view { const Char* name; const T& value; named_arg(const Char* n, const T& v) : name(n), value(v) {} }; template struct named_arg_info { const Char* name; int id; }; template struct arg_data { // args_[0].named_args points to named_args_ to avoid bloating format_args. // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)]; named_arg_info named_args_[NUM_NAMED_ARGS]; template arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} arg_data(const arg_data& other) = delete; auto args() const -> const T* { return args_ + 1; } auto named_args() -> named_arg_info* { return named_args_; } }; template struct arg_data { // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. T args_[NUM_ARGS != 0 ? NUM_ARGS : +1]; template FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {} FMT_CONSTEXPR FMT_INLINE auto args() const -> const T* { return args_; } FMT_CONSTEXPR FMT_INLINE auto named_args() -> std::nullptr_t { return nullptr; } }; template inline void init_named_args(named_arg_info*, int, int) {} template struct is_named_arg : std::false_type {}; template struct is_statically_named_arg : std::false_type {}; template struct is_named_arg> : std::true_type {}; template ::value)> void init_named_args(named_arg_info* named_args, int arg_count, int named_arg_count, const T&, const Tail&... args) { init_named_args(named_args, arg_count + 1, named_arg_count, args...); } template ::value)> void init_named_args(named_arg_info* named_args, int arg_count, int named_arg_count, const T& arg, const Tail&... args) { named_args[named_arg_count++] = {arg.name, arg_count}; init_named_args(named_args, arg_count + 1, named_arg_count, args...); } template FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int, const Args&...) {} template constexpr auto count() -> size_t { return B ? 1 : 0; } template constexpr auto count() -> size_t { return (B1 ? 1 : 0) + count(); } template constexpr auto count_named_args() -> size_t { return count::value...>(); } enum class type { none_type, // Integer types should go first, int_type, uint_type, long_long_type, ulong_long_type, int128_type, uint128_type, bool_type, char_type, last_integer_type = char_type, // followed by floating-point types. float_type, double_type, long_double_type, last_numeric_type = long_double_type, cstring_type, string_type, pointer_type, custom_type }; // Maps core type T to the corresponding type enum constant. template struct type_constant : std::integral_constant {}; #define FMT_TYPE_CONSTANT(Type, constant) \ template \ struct type_constant \ : std::integral_constant {} FMT_TYPE_CONSTANT(int, int_type); FMT_TYPE_CONSTANT(unsigned, uint_type); FMT_TYPE_CONSTANT(long long, long_long_type); FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); FMT_TYPE_CONSTANT(int128_t, int128_type); FMT_TYPE_CONSTANT(uint128_t, uint128_type); FMT_TYPE_CONSTANT(bool, bool_type); FMT_TYPE_CONSTANT(Char, char_type); FMT_TYPE_CONSTANT(float, float_type); FMT_TYPE_CONSTANT(double, double_type); FMT_TYPE_CONSTANT(long double, long_double_type); FMT_TYPE_CONSTANT(const Char*, cstring_type); FMT_TYPE_CONSTANT(basic_string_view, string_type); FMT_TYPE_CONSTANT(const void*, pointer_type); constexpr bool is_integral_type(type t) { return t > type::none_type && t <= type::last_integer_type; } constexpr bool is_arithmetic_type(type t) { return t > type::none_type && t <= type::last_numeric_type; } template struct string_value { const Char* data; size_t size; }; template struct named_arg_value { const named_arg_info* data; size_t size; }; template struct custom_value { using parse_context = typename Context::parse_context_type; const void* value; void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); }; // A formatting argument value. template class value { public: using char_type = typename Context::char_type; union { monostate no_value; int int_value; unsigned uint_value; long long long_long_value; unsigned long long ulong_long_value; int128_t int128_value; uint128_t uint128_value; bool bool_value; char_type char_value; float float_value; double double_value; long double long_double_value; const void* pointer; string_value string; custom_value custom; named_arg_value named_args; }; constexpr FMT_INLINE value() : no_value() {} constexpr FMT_INLINE value(int val) : int_value(val) {} constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} constexpr FMT_INLINE value(long long val) : long_long_value(val) {} constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} FMT_INLINE value(int128_t val) : int128_value(val) {} FMT_INLINE value(uint128_t val) : uint128_value(val) {} FMT_INLINE value(float val) : float_value(val) {} FMT_INLINE value(double val) : double_value(val) {} FMT_INLINE value(long double val) : long_double_value(val) {} constexpr FMT_INLINE value(bool val) : bool_value(val) {} constexpr FMT_INLINE value(char_type val) : char_value(val) {} FMT_CONSTEXPR FMT_INLINE value(const char_type* val) { string.data = val; if (is_constant_evaluated()) string.size = {}; } FMT_CONSTEXPR FMT_INLINE value(basic_string_view val) { string.data = val.data(); string.size = val.size(); } FMT_INLINE value(const void* val) : pointer(val) {} FMT_INLINE value(const named_arg_info* args, size_t size) : named_args{args, size} {} template FMT_CONSTEXPR FMT_INLINE value(const T& val) { custom.value = &val; // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter` for `format` and // `printf_formatter` for `printf`. custom.format = format_custom_arg< T, conditional_t::value, typename Context::template formatter_type, fallback_formatter>>; } private: // Formats an argument of a custom type, such as a user-defined class. template static void format_custom_arg(const void* arg, typename Context::parse_context_type& parse_ctx, Context& ctx) { Formatter f; parse_ctx.advance_to(f.parse(parse_ctx)); ctx.advance_to(f.format(*static_cast(arg), ctx)); } }; template FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg; // To minimize the number of types we need to deal with, long is translated // either to int or to long long depending on its size. enum { long_short = sizeof(long) == sizeof(int) }; using long_type = conditional_t; using ulong_type = conditional_t; struct unformattable {}; // Maps formatting arguments to core types. template struct arg_mapper { using char_type = typename Context::char_type; FMT_CONSTEXPR FMT_INLINE auto map(signed char val) -> int { return val; } FMT_CONSTEXPR FMT_INLINE auto map(unsigned char val) -> unsigned { return val; } FMT_CONSTEXPR FMT_INLINE auto map(short val) -> int { return val; } FMT_CONSTEXPR FMT_INLINE auto map(unsigned short val) -> unsigned { return val; } FMT_CONSTEXPR FMT_INLINE auto map(int val) -> int { return val; } FMT_CONSTEXPR FMT_INLINE auto map(unsigned val) -> unsigned { return val; } FMT_CONSTEXPR FMT_INLINE auto map(long val) -> long_type { return val; } FMT_CONSTEXPR FMT_INLINE auto map(unsigned long val) -> ulong_type { return val; } FMT_CONSTEXPR FMT_INLINE auto map(long long val) -> long long { return val; } FMT_CONSTEXPR FMT_INLINE auto map(unsigned long long val) -> unsigned long long { return val; } FMT_CONSTEXPR FMT_INLINE auto map(int128_t val) -> int128_t { return val; } FMT_CONSTEXPR FMT_INLINE auto map(uint128_t val) -> uint128_t { return val; } FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; } template ::value)> FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type { static_assert( std::is_same::value || std::is_same::value, "mixing character types is disallowed"); return val; } FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; } FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; } FMT_CONSTEXPR FMT_INLINE auto map(long double val) -> long double { return val; } FMT_CONSTEXPR FMT_INLINE auto map(char_type* val) -> const char_type* { return val; } FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* { return val; } template ::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> basic_string_view { static_assert(std::is_same>::value, "mixing character types is disallowed"); return to_string_view(val); } template , T>::value && !is_string::value && !has_formatter::value && !has_fallback_formatter::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> basic_string_view { return basic_string_view(val); } template < typename T, FMT_ENABLE_IF( std::is_constructible, T>::value && !std::is_constructible, T>::value && !is_string::value && !has_formatter::value && !has_fallback_formatter::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> basic_string_view { return std_string_view(val); } FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) -> const char* { static_assert(std::is_same::value, "invalid string type"); return reinterpret_cast(val); } FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val) -> const char* { static_assert(std::is_same::value, "invalid string type"); return reinterpret_cast(val); } FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) -> const char* { const auto* const_val = val; return map(const_val); } FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) -> const char* { const auto* const_val = val; return map(const_val); } FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { return val; } FMT_CONSTEXPR FMT_INLINE auto map(std::nullptr_t val) -> const void* { return val; } // We use SFINAE instead of a const T* parameter to avoid conflicting with // the C array overload. template FMT_CONSTEXPR auto map(T) -> enable_if_t::value, int> { // Formatting of arbitrary pointers is disallowed. If you want to output // a pointer cast it to "void *" or "const void *". In particular, this // forbids formatting of "[const] volatile char *" which is printed as bool // by iostreams. static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); return 0; } template FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] { return values; } template ::value && !has_formatter::value && !has_fallback_formatter::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(std::declval().map( static_cast::type>(val))) { return map(static_cast::type>(val)); } template ::value && !is_char::value && (has_formatter::value || has_fallback_formatter::value))> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> const T& { return val; } template ::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) -> decltype(std::declval().map(named_arg.value)) { return map(named_arg.value); } auto map(...) -> unformattable { return {}; } }; // A type constant after applying arg_mapper. template using mapped_type_constant = type_constant().map(std::declval())), typename Context::char_type>; enum { packed_arg_bits = 4 }; // Maximum number of arguments with packed types. enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; FMT_END_DETAIL_NAMESPACE // An output iterator that appends to a buffer. // It is used to reduce symbol sizes for the common case. class appender : public std::back_insert_iterator> { using base = std::back_insert_iterator>; template friend auto get_buffer(appender out) -> detail::buffer& { return detail::get_container(out); } public: using std::back_insert_iterator>::back_insert_iterator; appender(base it) : base(it) {} using _Unchecked_type = appender; // Mark iterator as checked. auto operator++() -> appender& { base::operator++(); return *this; } auto operator++(int) -> appender { auto tmp = *this; ++*this; return tmp; } }; // A formatting argument. It is a trivially copyable/constructible type to // allow storage in basic_memory_buffer. template class basic_format_arg { private: detail::value value_; detail::type type_; template friend FMT_CONSTEXPR auto detail::make_arg(const T& value) -> basic_format_arg; template friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)); friend class basic_format_args; friend class dynamic_format_arg_store; using char_type = typename Context::char_type; template friend struct detail::arg_data; basic_format_arg(const detail::named_arg_info* args, size_t size) : value_(args, size) {} public: class handle { public: explicit handle(detail::custom_value custom) : custom_(custom) {} void format(typename Context::parse_context_type& parse_ctx, Context& ctx) const { custom_.format(custom_.value, parse_ctx, ctx); } private: detail::custom_value custom_; }; constexpr basic_format_arg() : type_(detail::type::none_type) {} constexpr explicit operator bool() const FMT_NOEXCEPT { return type_ != detail::type::none_type; } auto type() const -> detail::type { return type_; } auto is_integral() const -> bool { return detail::is_integral_type(type_); } auto is_arithmetic() const -> bool { return detail::is_arithmetic_type(type_); } }; /** \rst Visits an argument dispatching to the appropriate visit method based on the argument type. For example, if the argument type is ``double`` then ``vis(value)`` will be called with the value of type ``double``. \endrst */ template FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { switch (arg.type_) { case detail::type::none_type: break; case detail::type::int_type: return vis(arg.value_.int_value); case detail::type::uint_type: return vis(arg.value_.uint_value); case detail::type::long_long_type: return vis(arg.value_.long_long_value); case detail::type::ulong_long_type: return vis(arg.value_.ulong_long_value); case detail::type::int128_type: return vis(detail::convert_for_visit(arg.value_.int128_value)); case detail::type::uint128_type: return vis(detail::convert_for_visit(arg.value_.uint128_value)); case detail::type::bool_type: return vis(arg.value_.bool_value); case detail::type::char_type: return vis(arg.value_.char_value); case detail::type::float_type: return vis(arg.value_.float_value); case detail::type::double_type: return vis(arg.value_.double_value); case detail::type::long_double_type: return vis(arg.value_.long_double_value); case detail::type::cstring_type: return vis(arg.value_.string.data); case detail::type::string_type: using sv = basic_string_view; return vis(sv(arg.value_.string.data, arg.value_.string.size)); case detail::type::pointer_type: return vis(arg.value_.pointer); case detail::type::custom_type: return vis(typename basic_format_arg::handle(arg.value_.custom)); } return vis(monostate()); } FMT_BEGIN_DETAIL_NAMESPACE template auto copy_str(InputIt begin, InputIt end, appender out) -> appender { get_container(out).append(begin, end); return out; } #if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 // A workaround for gcc 4.8 to make void_t work in a SFINAE context. template struct void_t_impl { using type = void; }; template using void_t = typename detail::void_t_impl::type; #else template using void_t = void; #endif template struct is_output_iterator : std::false_type {}; template struct is_output_iterator< It, T, void_t::iterator_category, decltype(*std::declval() = std::declval())>> : std::true_type {}; template struct is_back_insert_iterator : std::false_type {}; template struct is_back_insert_iterator> : std::true_type {}; template struct is_contiguous_back_insert_iterator : std::false_type {}; template struct is_contiguous_back_insert_iterator> : is_contiguous {}; template <> struct is_contiguous_back_insert_iterator : std::true_type {}; // A type-erased reference to an std::locale to avoid heavy include. class locale_ref { private: const void* locale_; // A type-erased pointer to std::locale. public: constexpr locale_ref() : locale_(nullptr) {} template explicit locale_ref(const Locale& loc); explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; } template auto get() const -> Locale; }; template constexpr auto encode_types() -> unsigned long long { return 0; } template constexpr auto encode_types() -> unsigned long long { return static_cast(mapped_type_constant::value) | (encode_types() << packed_arg_bits); } template FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg { basic_format_arg arg; arg.type_ = mapped_type_constant::value; arg.value_ = arg_mapper().map(value); return arg; } // The type template parameter is there to avoid an ODR violation when using // a fallback formatter in one translation unit and an implicit conversion in // another (not recommended). template FMT_CONSTEXPR FMT_INLINE auto make_arg(const T& val) -> value { const auto& arg = arg_mapper().map(val); static_assert( !std::is_same::value, "Cannot format an argument. To make type T formattable provide a " "formatter specialization: https://fmt.dev/latest/api.html#udt"); return {arg}; } template inline auto make_arg(const T& value) -> basic_format_arg { return make_arg(value); } FMT_END_DETAIL_NAMESPACE // Formatting context. template class basic_format_context { public: /** The character type for the output. */ using char_type = Char; private: OutputIt out_; basic_format_args args_; detail::locale_ref loc_; public: using iterator = OutputIt; using format_arg = basic_format_arg; using parse_context_type = basic_format_parse_context; template using formatter_type = formatter; basic_format_context(basic_format_context&&) = default; basic_format_context(const basic_format_context&) = delete; void operator=(const basic_format_context&) = delete; /** Constructs a ``basic_format_context`` object. References to the arguments are stored in the object so make sure they have appropriate lifetimes. */ constexpr basic_format_context( OutputIt out, basic_format_args ctx_args, detail::locale_ref loc = detail::locale_ref()) : out_(out), args_(ctx_args), loc_(loc) {} constexpr auto arg(int id) const -> format_arg { return args_.get(id); } FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { return args_.get(name); } FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { return args_.get_id(name); } auto args() const -> const basic_format_args& { return args_; } FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } void on_error(const char* message) { error_handler().on_error(message); } // Returns an iterator to the beginning of the output range. FMT_CONSTEXPR auto out() -> iterator { return out_; } // Advances the begin iterator to ``it``. void advance_to(iterator it) { if (!detail::is_back_insert_iterator()) out_ = it; } FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } }; template using buffer_context = basic_format_context, Char>; using format_context = buffer_context; // Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. #define FMT_BUFFER_CONTEXT(Char) \ basic_format_context, Char> template using is_formattable = bool_constant< !std::is_same>().map( std::declval())), detail::unformattable>::value && !detail::has_fallback_formatter::value>; /** \rst An array of references to arguments. It can be implicitly converted into `~fmt::basic_format_args` for passing into type-erased formatting functions such as `~fmt::vformat`. \endrst */ template class format_arg_store #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 // Workaround a GCC template argument substitution bug. : public basic_format_args #endif { private: static const size_t num_args = sizeof...(Args); static const size_t num_named_args = detail::count_named_args(); static const bool is_packed = num_args <= detail::max_packed_args; using value_type = conditional_t, basic_format_arg>; detail::arg_data data_; friend class basic_format_args; static constexpr unsigned long long desc = (is_packed ? detail::encode_types() : detail::is_unpacked_bit | num_args) | (num_named_args != 0 ? static_cast(detail::has_named_args_bit) : 0); public: FMT_CONSTEXPR FMT_INLINE format_arg_store(const Args&... args) : #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args(*this), #endif data_{detail::make_arg< is_packed, Context, detail::mapped_type_constant::value>(args)...} { detail::init_named_args(data_.named_args(), 0, 0, args...); } }; /** \rst Constructs a `~fmt::format_arg_store` object that contains references to arguments and can be implicitly converted to `~fmt::format_args`. `Context` can be omitted in which case it defaults to `~fmt::context`. See `~fmt::arg` for lifetime considerations. \endrst */ template constexpr auto make_format_args(const Args&... args) -> format_arg_store { return {args...}; } /** \rst Returns a named argument to be used in a formatting function. It should only be used in a call to a formatting function or `dynamic_format_arg_store::push_back`. **Example**:: fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); \endrst */ template inline auto arg(const Char* name, const T& arg) -> detail::named_arg { static_assert(!detail::is_named_arg(), "nested named arguments"); return {name, arg}; } /** \rst A view of a collection of formatting arguments. To avoid lifetime issues it should only be used as a parameter type in type-erased functions such as ``vformat``:: void vlog(string_view format_str, format_args args); // OK format_args args = make_format_args(42); // Error: dangling reference \endrst */ template class basic_format_args { public: using size_type = int; using format_arg = basic_format_arg; private: // A descriptor that contains information about formatting arguments. // If the number of arguments is less or equal to max_packed_args then // argument types are passed in the descriptor. This reduces binary code size // per formatting function call. unsigned long long desc_; union { // If is_packed() returns true then argument values are stored in values_; // otherwise they are stored in args_. This is done to improve cache // locality and reduce compiled code size since storing larger objects // may require more code (at least on x86-64) even if the same amount of // data is actually copied to stack. It saves ~10% on the bloat test. const detail::value* values_; const format_arg* args_; }; constexpr auto is_packed() const -> bool { return (desc_ & detail::is_unpacked_bit) == 0; } auto has_named_args() const -> bool { return (desc_ & detail::has_named_args_bit) != 0; } FMT_CONSTEXPR auto type(int index) const -> detail::type { int shift = index * detail::packed_arg_bits; unsigned int mask = (1 << detail::packed_arg_bits) - 1; return static_cast((desc_ >> shift) & mask); } constexpr FMT_INLINE basic_format_args(unsigned long long desc, const detail::value* values) : desc_(desc), values_(values) {} constexpr basic_format_args(unsigned long long desc, const format_arg* args) : desc_(desc), args_(args) {} public: constexpr basic_format_args() : desc_(0), args_(nullptr) {} /** \rst Constructs a `basic_format_args` object from `~fmt::format_arg_store`. \endrst */ template constexpr FMT_INLINE basic_format_args( const format_arg_store& store) : basic_format_args(format_arg_store::desc, store.data_.args()) {} /** \rst Constructs a `basic_format_args` object from `~fmt::dynamic_format_arg_store`. \endrst */ constexpr FMT_INLINE basic_format_args( const dynamic_format_arg_store& store) : basic_format_args(store.get_types(), store.data()) {} /** \rst Constructs a `basic_format_args` object from a dynamic set of arguments. \endrst */ constexpr basic_format_args(const format_arg* args, int count) : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), args) {} /** Returns the argument with the specified id. */ FMT_CONSTEXPR auto get(int id) const -> format_arg { format_arg arg; if (!is_packed()) { if (id < max_size()) arg = args_[id]; return arg; } if (id >= detail::max_packed_args) return arg; arg.type_ = type(id); if (arg.type_ == detail::type::none_type) return arg; arg.value_ = values_[id]; return arg; } template auto get(basic_string_view name) const -> format_arg { int id = get_id(name); return id >= 0 ? get(id) : format_arg(); } template auto get_id(basic_string_view name) const -> int { if (!has_named_args()) return -1; const auto& named_args = (is_packed() ? values_[-1] : args_[-1].value_).named_args; for (size_t i = 0; i < named_args.size; ++i) { if (named_args.data[i].name == name) return named_args.data[i].id; } return -1; } auto max_size() const -> int { unsigned long long max_packed = detail::max_packed_args; return static_cast(is_packed() ? max_packed : desc_ & ~detail::is_unpacked_bit); } }; /** An alias to ``basic_format_args``. */ // A separate type would result in shorter symbols but break ABI compatibility // between clang and gcc on ARM (#1919). using format_args = basic_format_args; // We cannot use enum classes as bit fields because of a gcc bug // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414. namespace align { enum type { none, left, right, center, numeric }; } using align_t = align::type; namespace sign { enum type { none, minus, plus, space }; } using sign_t = sign::type; FMT_BEGIN_DETAIL_NAMESPACE void throw_format_error(const char* message); // Workaround an array initialization issue in gcc 4.8. template struct fill_t { private: enum { max_size = 4 }; Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)}; unsigned char size_ = 1; public: FMT_CONSTEXPR void operator=(basic_string_view s) { auto size = s.size(); if (size > max_size) return throw_format_error("invalid fill"); for (size_t i = 0; i < size; ++i) data_[i] = s[i]; size_ = static_cast(size); } constexpr auto size() const -> size_t { return size_; } constexpr auto data() const -> const Char* { return data_; } FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; } FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& { return data_[index]; } }; FMT_END_DETAIL_NAMESPACE // Format specifiers for built-in and string types. template struct basic_format_specs { int width; int precision; char type; align_t align : 4; sign_t sign : 3; bool alt : 1; // Alternate form ('#'). bool localized : 1; detail::fill_t fill; constexpr basic_format_specs() : width(0), precision(-1), type(0), align(align::none), sign(sign::none), alt(false), localized(false) {} }; using format_specs = basic_format_specs; FMT_BEGIN_DETAIL_NAMESPACE enum class arg_id_kind { none, index, name }; // An argument reference. template struct arg_ref { FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} FMT_CONSTEXPR explicit arg_ref(int index) : kind(arg_id_kind::index), val(index) {} FMT_CONSTEXPR explicit arg_ref(basic_string_view name) : kind(arg_id_kind::name), val(name) {} FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { kind = arg_id_kind::index; val.index = idx; return *this; } arg_id_kind kind; union value { FMT_CONSTEXPR value(int id = 0) : index{id} {} FMT_CONSTEXPR value(basic_string_view n) : name(n) {} int index; basic_string_view name; } val; }; // Format specifiers with width and precision resolved at formatting rather // than parsing time to allow re-using the same parsed specifiers with // different sets of arguments (precompilation of format strings). template struct dynamic_format_specs : basic_format_specs { arg_ref width_ref; arg_ref precision_ref; }; struct auto_id {}; // A format specifier handler that sets fields in basic_format_specs. template class specs_setter { protected: basic_format_specs& specs_; public: explicit FMT_CONSTEXPR specs_setter(basic_format_specs& specs) : specs_(specs) {} FMT_CONSTEXPR specs_setter(const specs_setter& other) : specs_(other.specs_) {} FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } FMT_CONSTEXPR void on_fill(basic_string_view fill) { specs_.fill = fill; } FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } FMT_CONSTEXPR void on_hash() { specs_.alt = true; } FMT_CONSTEXPR void on_localized() { specs_.localized = true; } FMT_CONSTEXPR void on_zero() { if (specs_.align == align::none) specs_.align = align::numeric; specs_.fill[0] = Char('0'); } FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } FMT_CONSTEXPR void on_precision(int precision) { specs_.precision = precision; } FMT_CONSTEXPR void end_precision() {} FMT_CONSTEXPR void on_type(Char type) { specs_.type = static_cast(type); } }; // Format spec handler that saves references to arguments representing dynamic // width and precision to be resolved at formatting time. template class dynamic_specs_handler : public specs_setter { public: using char_type = typename ParseContext::char_type; FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs& specs, ParseContext& ctx) : specs_setter(specs), specs_(specs), context_(ctx) {} FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) : specs_setter(other), specs_(other.specs_), context_(other.context_) {} template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { specs_.width_ref = make_arg_ref(arg_id); } template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { specs_.precision_ref = make_arg_ref(arg_id); } FMT_CONSTEXPR void on_error(const char* message) { context_.on_error(message); } private: dynamic_format_specs& specs_; ParseContext& context_; using arg_ref_type = arg_ref; FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type { context_.check_arg_id(arg_id); return arg_ref_type(arg_id); } FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type { return arg_ref_type(context_.next_arg_id()); } FMT_CONSTEXPR auto make_arg_ref(basic_string_view arg_id) -> arg_ref_type { context_.check_arg_id(arg_id); basic_string_view format_str( context_.begin(), to_unsigned(context_.end() - context_.begin())); return arg_ref_type(arg_id); } }; template constexpr bool is_ascii_letter(Char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } // Converts a character to ASCII. Returns a number > 127 on conversion failure. template ::value)> constexpr auto to_ascii(Char value) -> Char { return value; } template ::value)> constexpr auto to_ascii(Char value) -> typename std::underlying_type::type { return value; } template FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { if (const_check(sizeof(Char) != 1)) return 1; constexpr char lengths[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0}; int len = lengths[static_cast(*begin) >> 3]; // Compute the pointer to the next character early so that the next // iteration can start working on the next character. Neither Clang // nor GCC figure out this reordering on their own. return len + !len; } // Return the result via the out param to workaround gcc bug 77539. template FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { for (out = first; out != last; ++out) { if (*out == value) return true; } return false; } template <> inline auto find(const char* first, const char* last, char value, const char*& out) -> bool { out = static_cast( std::memchr(first, value, to_unsigned(last - first))); return out != nullptr; } // Parses the range [begin, end) as an unsigned integer. This function assumes // that the range is non-empty and the first character is a digit. template FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, int error_value) noexcept -> int { FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); unsigned value = 0, prev = 0; auto p = begin; do { prev = value; value = value * 10 + unsigned(*p - '0'); ++p; } while (p != end && '0' <= *p && *p <= '9'); auto num_digits = p - begin; begin = p; if (num_digits <= std::numeric_limits::digits10) return static_cast(value); // Check for overflow. const unsigned max = to_unsigned((std::numeric_limits::max)()); return num_digits == std::numeric_limits::digits10 + 1 && prev * 10ull + unsigned(p[-1] - '0') <= max ? static_cast(value) : error_value; } // Parses fill and alignment. template FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, Handler&& handler) -> const Char* { FMT_ASSERT(begin != end, ""); auto align = align::none; auto p = begin + code_point_length(begin); if (p >= end) p = begin; for (;;) { switch (to_ascii(*p)) { case '<': align = align::left; break; case '>': align = align::right; break; case '^': align = align::center; break; default: break; } if (align != align::none) { if (p != begin) { auto c = *begin; if (c == '{') return handler.on_error("invalid fill character '{'"), begin; handler.on_fill(basic_string_view(begin, to_unsigned(p - begin))); begin = p + 1; } else ++begin; handler.on_align(align); break; } else if (p == begin) { break; } p = begin; } return begin; } template FMT_CONSTEXPR bool is_name_start(Char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; } template FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, IDHandler&& handler) -> const Char* { FMT_ASSERT(begin != end, ""); Char c = *begin; if (c >= '0' && c <= '9') { int index = 0; if (c != '0') index = parse_nonnegative_int(begin, end, (std::numeric_limits::max)()); else ++begin; if (begin == end || (*begin != '}' && *begin != ':')) handler.on_error("invalid format string"); else handler(index); return begin; } if (!is_name_start(c)) { handler.on_error("invalid format string"); return begin; } auto it = begin; do { ++it; } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); handler(basic_string_view(begin, to_unsigned(it - begin))); return it; } template FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, IDHandler&& handler) -> const Char* { Char c = *begin; if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); handler(); return begin; } template FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, Handler&& handler) -> const Char* { using detail::auto_id; struct width_adapter { Handler& handler; FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } FMT_CONSTEXPR void operator()(basic_string_view id) { handler.on_dynamic_width(id); } FMT_CONSTEXPR void on_error(const char* message) { if (message) handler.on_error(message); } }; FMT_ASSERT(begin != end, ""); if ('0' <= *begin && *begin <= '9') { int width = parse_nonnegative_int(begin, end, -1); if (width != -1) handler.on_width(width); else handler.on_error("number is too big"); } else if (*begin == '{') { ++begin; if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); if (begin == end || *begin != '}') return handler.on_error("invalid format string"), begin; ++begin; } return begin; } template FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, Handler&& handler) -> const Char* { using detail::auto_id; struct precision_adapter { Handler& handler; FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } FMT_CONSTEXPR void operator()(basic_string_view id) { handler.on_dynamic_precision(id); } FMT_CONSTEXPR void on_error(const char* message) { if (message) handler.on_error(message); } }; ++begin; auto c = begin != end ? *begin : Char(); if ('0' <= c && c <= '9') { auto precision = parse_nonnegative_int(begin, end, -1); if (precision != -1) handler.on_precision(precision); else handler.on_error("number is too big"); } else if (c == '{') { ++begin; if (begin != end) begin = parse_arg_id(begin, end, precision_adapter{handler}); if (begin == end || *begin++ != '}') return handler.on_error("invalid format string"), begin; } else { return handler.on_error("missing precision specifier"), begin; } handler.end_precision(); return begin; } // Parses standard format specifiers and sends notifications about parsed // components to handler. template FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, const Char* end, SpecHandler&& handler) -> const Char* { if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && *begin != 'L') { handler.on_type(*begin++); return begin; } if (begin == end) return begin; begin = parse_align(begin, end, handler); if (begin == end) return begin; // Parse sign. switch (to_ascii(*begin)) { case '+': handler.on_sign(sign::plus); ++begin; break; case '-': handler.on_sign(sign::minus); ++begin; break; case ' ': handler.on_sign(sign::space); ++begin; break; default: break; } if (begin == end) return begin; if (*begin == '#') { handler.on_hash(); if (++begin == end) return begin; } // Parse zero flag. if (*begin == '0') { handler.on_zero(); if (++begin == end) return begin; } begin = parse_width(begin, end, handler); if (begin == end) return begin; // Parse precision. if (*begin == '.') { begin = parse_precision(begin, end, handler); if (begin == end) return begin; } if (*begin == 'L') { handler.on_localized(); ++begin; } // Parse type. if (begin != end && *begin != '}') handler.on_type(*begin++); return begin; } template FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, Handler&& handler) -> const Char* { struct id_adapter { Handler& handler; int arg_id; FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } FMT_CONSTEXPR void operator()(basic_string_view id) { arg_id = handler.on_arg_id(id); } FMT_CONSTEXPR void on_error(const char* message) { if (message) handler.on_error(message); } }; ++begin; if (begin == end) return handler.on_error("invalid format string"), end; if (*begin == '}') { handler.on_replacement_field(handler.on_arg_id(), begin); } else if (*begin == '{') { handler.on_text(begin, begin + 1); } else { auto adapter = id_adapter{handler, 0}; begin = parse_arg_id(begin, end, adapter); Char c = begin != end ? *begin : Char(); if (c == '}') { handler.on_replacement_field(adapter.arg_id, begin); } else if (c == ':') { begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); if (begin == end || *begin != '}') return handler.on_error("unknown format specifier"), end; } else { return handler.on_error("missing '}' in format string"), end; } } return begin + 1; } template FMT_CONSTEXPR FMT_INLINE void parse_format_string( basic_string_view format_str, Handler&& handler) { // this is most likely a name-lookup defect in msvc's modules implementation using detail::find; auto begin = format_str.data(); auto end = begin + format_str.size(); if (end - begin < 32) { // Use a simple loop instead of memchr for small strings. const Char* p = begin; while (p != end) { auto c = *p++; if (c == '{') { handler.on_text(begin, p - 1); begin = p = parse_replacement_field(p - 1, end, handler); } else if (c == '}') { if (p == end || *p != '}') return handler.on_error("unmatched '}' in format string"); handler.on_text(begin, p); begin = ++p; } } handler.on_text(begin, end); return; } struct writer { FMT_CONSTEXPR void operator()(const Char* pbegin, const Char* pend) { if (pbegin == pend) return; for (;;) { const Char* p = nullptr; if (!find(pbegin, pend, '}', p)) return handler_.on_text(pbegin, pend); ++p; if (p == pend || *p != '}') return handler_.on_error("unmatched '}' in format string"); handler_.on_text(pbegin, p); pbegin = p + 1; } } Handler& handler_; } write{handler}; while (begin != end) { // Doing two passes with memchr (one for '{' and another for '}') is up to // 2.5x faster than the naive one-pass implementation on big format strings. const Char* p = begin; if (*begin != '{' && !find(begin + 1, end, '{', p)) return write(begin, end); write(begin, p); begin = parse_replacement_field(p, end, handler); } } template FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) -> decltype(ctx.begin()) { using char_type = typename ParseContext::char_type; using context = buffer_context; using mapped_type = conditional_t< mapped_type_constant::value != type::custom_type, decltype(arg_mapper().map(std::declval())), T>; auto f = conditional_t::value, formatter, fallback_formatter>(); return f.parse(ctx); } // A parse context with extra argument id checks. It is only used at compile // time because adding checks at runtime would introduce substantial overhead // and would be redundant since argument ids are checked when arguments are // retrieved anyway. template class compile_parse_context : public basic_format_parse_context { private: int num_args_; using base = basic_format_parse_context; public: explicit FMT_CONSTEXPR compile_parse_context( basic_string_view format_str, int num_args = (std::numeric_limits::max)(), ErrorHandler eh = {}) : base(format_str, eh), num_args_(num_args) {} FMT_CONSTEXPR auto next_arg_id() -> int { int id = base::next_arg_id(); if (id >= num_args_) this->on_error("argument not found"); return id; } FMT_CONSTEXPR void check_arg_id(int id) { base::check_arg_id(id); if (id >= num_args_) this->on_error("argument not found"); } using base::check_arg_id; }; template FMT_CONSTEXPR void check_int_type_spec(char spec, ErrorHandler&& eh) { switch (spec) { case 0: case 'd': case 'x': case 'X': case 'b': case 'B': case 'o': case 'c': break; default: eh.on_error("invalid type specifier"); break; } } // Checks char specs and returns true if the type spec is char (and not int). template FMT_CONSTEXPR auto check_char_specs(const basic_format_specs& specs, ErrorHandler&& eh = {}) -> bool { if (specs.type && specs.type != 'c') { check_int_type_spec(specs.type, eh); return false; } if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) eh.on_error("invalid format specifier for char"); return true; } // A floating-point presentation format. enum class float_format : unsigned char { general, // General: exponent notation or fixed point based on magnitude. exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. fixed, // Fixed point with the default precision of 6, e.g. 0.0012. hex }; struct float_specs { int precision; float_format format : 8; sign_t sign : 8; bool upper : 1; bool locale : 1; bool binary32 : 1; bool use_grisu : 1; bool showpoint : 1; }; template FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs& specs, ErrorHandler&& eh = {}) -> float_specs { auto result = float_specs(); result.showpoint = specs.alt; result.locale = specs.localized; switch (specs.type) { case 0: result.format = float_format::general; break; case 'G': result.upper = true; FMT_FALLTHROUGH; case 'g': result.format = float_format::general; break; case 'E': result.upper = true; FMT_FALLTHROUGH; case 'e': result.format = float_format::exp; result.showpoint |= specs.precision != 0; break; case 'F': result.upper = true; FMT_FALLTHROUGH; case 'f': result.format = float_format::fixed; result.showpoint |= specs.precision != 0; break; case 'A': result.upper = true; FMT_FALLTHROUGH; case 'a': result.format = float_format::hex; break; default: eh.on_error("invalid type specifier"); break; } return result; } template FMT_CONSTEXPR auto check_cstring_type_spec(Char spec, ErrorHandler&& eh = {}) -> bool { if (spec == 0 || spec == 's') return true; if (spec != 'p') eh.on_error("invalid type specifier"); return false; } template FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler&& eh) { if (spec != 0 && spec != 's') eh.on_error("invalid type specifier"); } template FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler&& eh) { if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier"); } // A parse_format_specs handler that checks if specifiers are consistent with // the argument type. template class specs_checker : public Handler { private: detail::type arg_type_; FMT_CONSTEXPR void require_numeric_argument() { if (!is_arithmetic_type(arg_type_)) this->on_error("format specifier requires numeric argument"); } public: FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) : Handler(handler), arg_type_(arg_type) {} FMT_CONSTEXPR void on_align(align_t align) { if (align == align::numeric) require_numeric_argument(); Handler::on_align(align); } FMT_CONSTEXPR void on_sign(sign_t s) { require_numeric_argument(); if (is_integral_type(arg_type_) && arg_type_ != type::int_type && arg_type_ != type::long_long_type && arg_type_ != type::char_type) { this->on_error("format specifier requires signed argument"); } Handler::on_sign(s); } FMT_CONSTEXPR void on_hash() { require_numeric_argument(); Handler::on_hash(); } FMT_CONSTEXPR void on_localized() { require_numeric_argument(); Handler::on_localized(); } FMT_CONSTEXPR void on_zero() { require_numeric_argument(); Handler::on_zero(); } FMT_CONSTEXPR void end_precision() { if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) this->on_error("precision not allowed for this argument type"); } }; constexpr int invalid_arg_index = -1; #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS template constexpr auto get_arg_index_by_name(basic_string_view name) -> int { if constexpr (detail::is_statically_named_arg()) { if (name == T::name) return N; } if constexpr (sizeof...(Args) > 0) return get_arg_index_by_name(name); (void)name; // Workaround an MSVC bug about "unused" parameter. return invalid_arg_index; } #endif template FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS if constexpr (sizeof...(Args) > 0) return get_arg_index_by_name<0, Args...>(name); #endif (void)name; return invalid_arg_index; } template class format_string_checker { private: using parse_context_type = compile_parse_context; enum { num_args = sizeof...(Args) }; // Format specifier parsing function. using parse_func = const Char* (*)(parse_context_type&); parse_context_type context_; parse_func parse_funcs_[num_args > 0 ? num_args : 1]; public: explicit FMT_CONSTEXPR format_string_checker( basic_string_view format_str, ErrorHandler eh) : context_(format_str, num_args, eh), parse_funcs_{&parse_format_specs...} {} FMT_CONSTEXPR void on_text(const Char*, const Char*) {} FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } FMT_CONSTEXPR auto on_arg_id(int id) -> int { return context_.check_arg_id(id), id; } FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS auto index = get_arg_index_by_name(id); if (index == invalid_arg_index) on_error("named argument is not found"); return context_.check_arg_id(index), index; #else (void)id; on_error("compile-time checks for named arguments require C++20 support"); return 0; #endif } FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) -> const Char* { context_.advance_to(context_.begin() + (begin - &*context_.begin())); // id >= 0 check is a workaround for gcc 10 bug (#2065). return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; } FMT_CONSTEXPR void on_error(const char* message) { context_.on_error(message); } }; template ::value), int>> void check_format_string(S format_str) { FMT_CONSTEXPR auto s = to_string_view(format_str); using checker = format_string_checker...>; FMT_CONSTEXPR bool invalid_format = (parse_format_string(s, checker(s, {})), true); (void)invalid_format; } template void vformat_to( buffer& buf, basic_string_view fmt, basic_format_args)> args, detail::locale_ref loc = {}); FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); #ifndef _WIN32 inline void vprint_mojibake(std::FILE*, string_view, format_args) {} #endif FMT_END_DETAIL_NAMESPACE // A formatter specialization for the core types corresponding to detail::type // constants. template struct formatter::value != detail::type::custom_type>> { private: detail::dynamic_format_specs specs_; public: // Parses format specifiers stopping either at the end of the range or at the // terminating '}'. template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { auto begin = ctx.begin(), end = ctx.end(); if (begin == end) return begin; using handler_type = detail::dynamic_specs_handler; auto type = detail::type_constant::value; auto checker = detail::specs_checker(handler_type(specs_, ctx), type); auto it = detail::parse_format_specs(begin, end, checker); auto eh = ctx.error_handler(); switch (type) { case detail::type::none_type: FMT_ASSERT(false, "invalid argument type"); break; case detail::type::bool_type: if (!specs_.type || specs_.type == 's') break; FMT_FALLTHROUGH; case detail::type::int_type: case detail::type::uint_type: case detail::type::long_long_type: case detail::type::ulong_long_type: case detail::type::int128_type: case detail::type::uint128_type: detail::check_int_type_spec(specs_.type, eh); break; case detail::type::char_type: detail::check_char_specs(specs_, eh); break; case detail::type::float_type: if (detail::const_check(FMT_USE_FLOAT)) detail::parse_float_type_spec(specs_, eh); else FMT_ASSERT(false, "float support disabled"); break; case detail::type::double_type: if (detail::const_check(FMT_USE_DOUBLE)) detail::parse_float_type_spec(specs_, eh); else FMT_ASSERT(false, "double support disabled"); break; case detail::type::long_double_type: if (detail::const_check(FMT_USE_LONG_DOUBLE)) detail::parse_float_type_spec(specs_, eh); else FMT_ASSERT(false, "long double support disabled"); break; case detail::type::cstring_type: detail::check_cstring_type_spec(specs_.type, eh); break; case detail::type::string_type: detail::check_string_type_spec(specs_.type, eh); break; case detail::type::pointer_type: detail::check_pointer_type_spec(specs_.type, eh); break; case detail::type::custom_type: // Custom format specifiers are checked in parse functions of // formatter specializations. break; } return it; } template FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const -> decltype(ctx.out()); }; template struct basic_runtime { basic_string_view str; }; template class basic_format_string { private: basic_string_view str_; public: template >::value)> FMT_CONSTEVAL basic_format_string(const S& s) : str_(s) { static_assert( detail::count< (std::is_base_of>::value && std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); #ifdef FMT_HAS_CONSTEVAL if constexpr (detail::count_named_args() == 0) { using checker = detail::format_string_checker...>; detail::parse_format_string(str_, checker(s, {})); } #else detail::check_format_string(s); #endif } basic_format_string(basic_runtime r) : str_(r.str) {} FMT_INLINE operator basic_string_view() const { return str_; } }; #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 // Workaround broken conversion on older gcc. template using format_string = string_view; template auto runtime(const S& s) -> basic_string_view> { return s; } #else template using format_string = basic_format_string...>; // Creates a runtime format string. template auto runtime(const S& s) -> basic_runtime> { return {{s}}; } #endif FMT_API auto vformat(string_view fmt, format_args args) -> std::string; /** \rst Formats ``args`` according to specifications in ``fmt`` and returns the result as a string. **Example**:: #include std::string message = fmt::format("The answer is {}", 42); \endrst */ template FMT_INLINE auto format(format_string fmt, T&&... args) -> std::string { return vformat(fmt, fmt::make_format_args(args...)); } /** Formats a string and writes the output to ``out``. */ template ::value)> auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { using detail::get_buffer; auto&& buf = get_buffer(out); detail::vformat_to(buf, string_view(fmt), args); return detail::get_iterator(buf); } /** \rst Formats ``args`` according to specifications in ``fmt``, writes the result to the output iterator ``out`` and returns the iterator past the end of the output range. **Example**:: auto out = std::vector(); fmt::format_to(std::back_inserter(out), "{}", 42); \endrst */ template ::value)> FMT_INLINE auto format_to(OutputIt out, format_string fmt, T&&... args) -> OutputIt { return vformat_to(out, fmt, fmt::make_format_args(args...)); } template struct format_to_n_result { /** Iterator past the end of the output range. */ OutputIt out; /** Total (not truncated) output size. */ size_t size; }; template ::value)> auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) -> format_to_n_result { using buffer = detail::iterator_buffer; auto buf = buffer(out, n); detail::vformat_to(buf, fmt, args); return {buf.out(), buf.count()}; } /** \rst Formats ``args`` according to specifications in ``fmt``, writes up to ``n`` characters of the result to the output iterator ``out`` and returns the total (not truncated) output size and the iterator past the end of the output range. \endrst */ template ::value)> FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, const T&... args) -> format_to_n_result { return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); } /** Returns the number of chars in the output of ``format(fmt, args...)``. */ template FMT_INLINE auto formatted_size(format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...)); return buf.count(); } FMT_API void vprint(string_view fmt, format_args args); FMT_API void vprint(std::FILE* f, string_view fmt, format_args args); /** \rst Formats ``args`` according to specifications in ``fmt`` and writes the output to ``stdout``. **Example**:: fmt::print("Elapsed time: {0:.2f} seconds", 1.23); \endrst */ template FMT_INLINE void print(format_string fmt, T&&... args) { const auto& vargs = fmt::make_format_args(args...); return detail::is_utf8() ? vprint(fmt, vargs) : detail::vprint_mojibake(stdout, fmt, vargs); } /** \rst Formats ``args`` according to specifications in ``fmt`` and writes the output to the file ``f``. **Example**:: fmt::print(stderr, "Don't {}!", "panic"); \endrst */ template FMT_INLINE void print(std::FILE* f, format_string fmt, T&&... args) { const auto& vargs = fmt::make_format_args(args...); return detail::is_utf8() ? vprint(f, fmt, vargs) : detail::vprint_mojibake(f, fmt, vargs); } FMT_MODULE_EXPORT_END FMT_GCC_PRAGMA("GCC pop_options") FMT_END_NAMESPACE #ifdef FMT_HEADER_ONLY # include "format.h" #endif #endif // FMT_CORE_H_ fastnetmon-1.2.8/src/fmt/format-inl.h000066400000000000000000003127701472727706000175240ustar00rootroot00000000000000// Formatting library for C++ - implementation // // Copyright (c) 2012 - 2016, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_ #include #include #include // errno #include #include #include #include // std::memmove #include #include #ifndef FMT_STATIC_THOUSANDS_SEPARATOR # include #endif #ifdef _WIN32 # include // _isatty #endif #include "format.h" FMT_BEGIN_NAMESPACE namespace detail { FMT_FUNC void assert_fail(const char* file, int line, const char* message) { // Use unchecked std::fprintf to avoid triggering another assertion when // writing to stderr fails std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); // Chosen instead of std::abort to satisfy Clang in CUDA mode during device // code pass. std::terminate(); } #ifndef _MSC_VER # define FMT_SNPRINTF snprintf #else // _MSC_VER inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) { va_list args; va_start(args, format); int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); va_end(args); return result; } # define FMT_SNPRINTF fmt_snprintf #endif // _MSC_VER FMT_FUNC void format_error_code(detail::buffer& out, int error_code, string_view message) FMT_NOEXCEPT { // Report error code making sure that the output fits into // inline_buffer_size to avoid dynamic memory allocation and potential // bad_alloc. out.try_resize(0); static const char SEP[] = ": "; static const char ERROR_STR[] = "error "; // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; auto abs_value = static_cast>(error_code); if (detail::is_negative(error_code)) { abs_value = 0 - abs_value; ++error_code_size; } error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); auto it = buffer_appender(out); if (message.size() <= inline_buffer_size - error_code_size) format_to(it, FMT_STRING("{}{}"), message, SEP); format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); FMT_ASSERT(out.size() <= inline_buffer_size, ""); } FMT_FUNC void report_error(format_func func, int error_code, const char* message) FMT_NOEXCEPT { memory_buffer full_message; func(full_message, error_code, message); // Don't use fwrite_fully because the latter may throw. if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) std::fputc('\n', stderr); } // A wrapper around fwrite that throws on error. inline void fwrite_fully(const void* ptr, size_t size, size_t count, FILE* stream) { size_t written = std::fwrite(ptr, size, count, stream); if (written < count) FMT_THROW(system_error(errno, "cannot write to file")); } #ifndef FMT_STATIC_THOUSANDS_SEPARATOR template locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { static_assert(std::is_same::value, ""); } template Locale locale_ref::get() const { static_assert(std::is_same::value, ""); return locale_ ? *static_cast(locale_) : std::locale(); } template FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { auto& facet = std::use_facet>(loc.get()); auto grouping = facet.grouping(); auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); return {std::move(grouping), thousands_sep}; } template FMT_FUNC Char decimal_point_impl(locale_ref loc) { return std::use_facet>(loc.get()) .decimal_point(); } #else template FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result { return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR}; } template FMT_FUNC Char decimal_point_impl(locale_ref) { return '.'; } #endif } // namespace detail #if !FMT_MSC_VER FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default; #endif FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str, format_args args) { auto ec = std::error_code(error_code, std::generic_category()); return std::system_error(ec, vformat(format_str, args)); } namespace detail { template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) { // fallback_uintptr is always stored in little endian. int i = static_cast(sizeof(void*)) - 1; while (i > 0 && n.value[i] == 0) --i; auto char_digits = std::numeric_limits::digits / 4; return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1; } #if __cplusplus < 201703L template constexpr const char basic_data::digits[][2]; template constexpr const char basic_data::hex_digits[]; template constexpr const char basic_data::signs[]; template constexpr const unsigned basic_data::prefixes[]; template constexpr const char basic_data::left_padding_shifts[]; template constexpr const char basic_data::right_padding_shifts[]; #endif template struct bits { static FMT_CONSTEXPR_DECL const int value = static_cast(sizeof(T) * std::numeric_limits::digits); }; class fp; template fp normalize(fp value); // Lower (upper) boundary is a value half way between a floating-point value // and its predecessor (successor). Boundaries have the same exponent as the // value so only significands are stored. struct boundaries { uint64_t lower; uint64_t upper; }; // A handmade floating-point number f * pow(2, e). class fp { private: using significand_type = uint64_t; template using is_supported_float = bool_constant; public: significand_type f; int e; // All sizes are in bits. // Subtract 1 to account for an implicit most significant bit in the // normalized form. static FMT_CONSTEXPR_DECL const int double_significand_size = std::numeric_limits::digits - 1; static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = 1ULL << double_significand_size; static FMT_CONSTEXPR_DECL const int significand_size = bits::value; fp() : f(0), e(0) {} fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} // Constructs fp from an IEEE754 double. It is a template to prevent compile // errors on platforms where double is not IEEE754. template explicit fp(Double d) { assign(d); } // Assigns d to this and return true iff predecessor is closer than successor. template ::value)> bool assign(Float d) { // Assume float is in the format [sign][exponent][significand]. using limits = std::numeric_limits; const int float_significand_size = limits::digits - 1; const int exponent_size = bits::value - float_significand_size - 1; // -1 for sign const uint64_t float_implicit_bit = 1ULL << float_significand_size; const uint64_t significand_mask = float_implicit_bit - 1; const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask; const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; constexpr bool is_double = sizeof(Float) == sizeof(uint64_t); auto u = bit_cast>(d); f = u & significand_mask; int biased_e = static_cast((u & exponent_mask) >> float_significand_size); // Predecessor is closer if d is a normalized power of 2 (f == 0) other than // the smallest normalized number (biased_e > 1). bool is_predecessor_closer = f == 0 && biased_e > 1; if (biased_e != 0) f += float_implicit_bit; else biased_e = 1; // Subnormals use biased exponent 1 (min exponent). e = biased_e - exponent_bias - float_significand_size; return is_predecessor_closer; } template ::value)> bool assign(Float) { *this = fp(); return false; } }; // Normalizes the value converted from double and multiplied by (1 << SHIFT). template fp normalize(fp value) { // Handle subnormals. const auto shifted_implicit_bit = fp::implicit_bit << SHIFT; while ((value.f & shifted_implicit_bit) == 0) { value.f <<= 1; --value.e; } // Subtract 1 to account for hidden bit. const auto offset = fp::significand_size - fp::double_significand_size - SHIFT - 1; value.f <<= offset; value.e -= offset; return value; } inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; } // Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { #if FMT_USE_INT128 auto product = static_cast<__uint128_t>(lhs) * rhs; auto f = static_cast(product >> 64); return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; #else // Multiply 32-bit parts of significands. uint64_t mask = (1ULL << 32) - 1; uint64_t a = lhs >> 32, b = lhs & mask; uint64_t c = rhs >> 32, d = rhs & mask; uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; // Compute mid 64-bit of result and round. uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); #endif } inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; } // Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its // (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. inline fp get_cached_power(int min_exponent, int& pow10_exponent) { // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. // These are generated by support/compute-powers.py. static constexpr const uint64_t pow10_significands[] = { 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, }; // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding // to significands above. static constexpr const int16_t pow10_exponents[] = { -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; const int shift = 32; const auto significand = static_cast(data::log10_2_significand); int index = static_cast( ((min_exponent + fp::significand_size - 1) * (significand >> shift) + ((int64_t(1) << shift) - 1)) // ceil >> 32 // arithmetic shift ); // Decimal exponent of the first (smallest) cached power of 10. const int first_dec_exp = -348; // Difference between 2 consecutive decimal exponents in cached powers of 10. const int dec_exp_step = 8; index = (index - first_dec_exp - 1) / dec_exp_step + 1; pow10_exponent = first_dec_exp + index * dec_exp_step; return {pow10_significands[index], pow10_exponents[index]}; } // A simple accumulator to hold the sums of terms in bigint::square if uint128_t // is not available. struct accumulator { uint64_t lower; uint64_t upper; accumulator() : lower(0), upper(0) {} explicit operator uint32_t() const { return static_cast(lower); } void operator+=(uint64_t n) { lower += n; if (lower < n) ++upper; } void operator>>=(int shift) { FMT_ASSERT(shift == 32, ""); (void)shift; lower = (upper << 32) | (lower >> 32); upper >>= 32; } }; class bigint { private: // A bigint is stored as an array of bigits (big digits), with bigit at index // 0 being the least significant one. using bigit = uint32_t; using double_bigit = uint64_t; enum { bigits_capacity = 32 }; basic_memory_buffer bigits_; int exp_; bigit operator[](int index) const { return bigits_[to_unsigned(index)]; } bigit& operator[](int index) { return bigits_[to_unsigned(index)]; } static FMT_CONSTEXPR_DECL const int bigit_bits = bits::value; friend struct formatter; void subtract_bigits(int index, bigit other, bigit& borrow) { auto result = static_cast((*this)[index]) - other - borrow; (*this)[index] = static_cast(result); borrow = static_cast(result >> (bigit_bits * 2 - 1)); } void remove_leading_zeros() { int num_bigits = static_cast(bigits_.size()) - 1; while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; bigits_.resize(to_unsigned(num_bigits + 1)); } // Computes *this -= other assuming aligned bigints and *this >= other. void subtract_aligned(const bigint& other) { FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); FMT_ASSERT(compare(*this, other) >= 0, ""); bigit borrow = 0; int i = other.exp_ - exp_; for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) subtract_bigits(i, other.bigits_[j], borrow); while (borrow > 0) subtract_bigits(i, 0, borrow); remove_leading_zeros(); } void multiply(uint32_t value) { const double_bigit wide_value = value; bigit carry = 0; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { double_bigit result = bigits_[i] * wide_value + carry; bigits_[i] = static_cast(result); carry = static_cast(result >> bigit_bits); } if (carry != 0) bigits_.push_back(carry); } void multiply(uint64_t value) { const bigit mask = ~bigit(0); const double_bigit lower = value & mask; const double_bigit upper = value >> bigit_bits; double_bigit carry = 0; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { double_bigit result = bigits_[i] * lower + (carry & mask); carry = bigits_[i] * upper + (result >> bigit_bits) + (carry >> bigit_bits); bigits_[i] = static_cast(result); } while (carry != 0) { bigits_.push_back(carry & mask); carry >>= bigit_bits; } } public: bigint() : exp_(0) {} explicit bigint(uint64_t n) { assign(n); } ~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); } bigint(const bigint&) = delete; void operator=(const bigint&) = delete; void assign(const bigint& other) { auto size = other.bigits_.size(); bigits_.resize(size); auto data = other.bigits_.data(); std::copy(data, data + size, make_checked(bigits_.data(), size)); exp_ = other.exp_; } void assign(uint64_t n) { size_t num_bigits = 0; do { bigits_[num_bigits++] = n & ~bigit(0); n >>= bigit_bits; } while (n != 0); bigits_.resize(num_bigits); exp_ = 0; } int num_bigits() const { return static_cast(bigits_.size()) + exp_; } FMT_NOINLINE bigint& operator<<=(int shift) { FMT_ASSERT(shift >= 0, ""); exp_ += shift / bigit_bits; shift %= bigit_bits; if (shift == 0) return *this; bigit carry = 0; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { bigit c = bigits_[i] >> (bigit_bits - shift); bigits_[i] = (bigits_[i] << shift) + carry; carry = c; } if (carry != 0) bigits_.push_back(carry); return *this; } template bigint& operator*=(Int value) { FMT_ASSERT(value > 0, ""); multiply(uint32_or_64_or_128_t(value)); return *this; } friend int compare(const bigint& lhs, const bigint& rhs) { int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); if (num_lhs_bigits != num_rhs_bigits) return num_lhs_bigits > num_rhs_bigits ? 1 : -1; int i = static_cast(lhs.bigits_.size()) - 1; int j = static_cast(rhs.bigits_.size()) - 1; int end = i - j; if (end < 0) end = 0; for (; i >= end; --i, --j) { bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; } if (i != j) return i > j ? 1 : -1; return 0; } // Returns compare(lhs1 + lhs2, rhs). friend int add_compare(const bigint& lhs1, const bigint& lhs2, const bigint& rhs) { int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits()); int num_rhs_bigits = rhs.num_bigits(); if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; if (max_lhs_bigits > num_rhs_bigits) return 1; auto get_bigit = [](const bigint& n, int i) -> bigit { return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; }; double_bigit borrow = 0; int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_); for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { double_bigit sum = static_cast(get_bigit(lhs1, i)) + get_bigit(lhs2, i); bigit rhs_bigit = get_bigit(rhs, i); if (sum > rhs_bigit + borrow) return 1; borrow = rhs_bigit + borrow - sum; if (borrow > 1) return -1; borrow <<= bigit_bits; } return borrow != 0 ? -1 : 0; } // Assigns pow(10, exp) to this bigint. void assign_pow10(int exp) { FMT_ASSERT(exp >= 0, ""); if (exp == 0) return assign(1); // Find the top bit. int bitmask = 1; while (exp >= bitmask) bitmask <<= 1; bitmask >>= 1; // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by // repeated squaring and multiplication. assign(5); bitmask >>= 1; while (bitmask != 0) { square(); if ((exp & bitmask) != 0) *this *= 5; bitmask >>= 1; } *this <<= exp; // Multiply by pow(2, exp) by shifting. } void square() { int num_bigits = static_cast(bigits_.size()); int num_result_bigits = 2 * num_bigits; basic_memory_buffer n(std::move(bigits_)); bigits_.resize(to_unsigned(num_result_bigits)); using accumulator_t = conditional_t; auto sum = accumulator_t(); for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { // Compute bigit at position bigit_index of the result by adding // cross-product terms n[i] * n[j] such that i + j == bigit_index. for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { // Most terms are multiplied twice which can be optimized in the future. sum += static_cast(n[i]) * n[j]; } (*this)[bigit_index] = static_cast(sum); sum >>= bits::value; // Compute the carry. } // Do the same for the top half. for (int bigit_index = num_bigits; bigit_index < num_result_bigits; ++bigit_index) { for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) sum += static_cast(n[i++]) * n[j--]; (*this)[bigit_index] = static_cast(sum); sum >>= bits::value; } --num_result_bigits; remove_leading_zeros(); exp_ *= 2; } // If this bigint has a bigger exponent than other, adds trailing zero to make // exponents equal. This simplifies some operations such as subtraction. void align(const bigint& other) { int exp_difference = exp_ - other.exp_; if (exp_difference <= 0) return; int num_bigits = static_cast(bigits_.size()); bigits_.resize(to_unsigned(num_bigits + exp_difference)); for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) bigits_[j] = bigits_[i]; std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); exp_ -= exp_difference; } // Divides this bignum by divisor, assigning the remainder to this and // returning the quotient. int divmod_assign(const bigint& divisor) { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); align(divisor); int quotient = 0; do { subtract_aligned(divisor); ++quotient; } while (compare(*this, divisor) >= 0); return quotient; } }; enum class round_direction { unknown, up, down }; // Given the divisor (normally a power of 10), the remainder = v % divisor for // some number v and the error, returns whether v should be rounded up, down, or // whether the rounding direction can't be determined due to error. // error should be less than divisor / 2. inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder, uint64_t error) { FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow. // Round down if (remainder + error) * 2 <= divisor. if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2) return round_direction::down; // Round up if (remainder - error) * 2 >= divisor. if (remainder >= error && remainder - error >= divisor - (remainder - error)) { return round_direction::up; } return round_direction::unknown; } namespace digits { enum result { more, // Generate more digits. done, // Done generating digits. error // Digit generation cancelled due to an error. }; } inline uint64_t power_of_10_64(int exp) { static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL), 10000000000000000000ULL}; return data[exp]; } // Generates output using the Grisu digit-gen algorithm. // error: the size of the region (lower, upper) outside of which numbers // definitely do not round to value (Delta in Grisu3). template FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, Handler& handler) { const fp one(1ULL << -value.e, value.e); // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be // zero because it contains a product of two 64-bit numbers with MSB set (due // to normalization) - 1, shifted right by at most 60 bits. auto integral = static_cast(value.f >> -one.e); FMT_ASSERT(integral != 0, ""); FMT_ASSERT(integral == value.f >> -one.e, ""); // The fractional part of scaled value (p2 in Grisu) c = value % one. uint64_t fractional = value.f & (one.f - 1); exp = count_digits(integral); // kappa in Grisu. // Divide by 10 to prevent overflow. auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e, value.f / 10, error * 10, exp); if (result != digits::more) return result; // Generate digits for the integral part. This can produce up to 10 digits. do { uint32_t digit = 0; auto divmod_integral = [&](uint32_t divisor) { digit = integral / divisor; integral %= divisor; }; // This optimization by Milo Yip reduces the number of integer divisions by // one per iteration. switch (exp) { case 10: divmod_integral(1000000000); break; case 9: divmod_integral(100000000); break; case 8: divmod_integral(10000000); break; case 7: divmod_integral(1000000); break; case 6: divmod_integral(100000); break; case 5: divmod_integral(10000); break; case 4: divmod_integral(1000); break; case 3: divmod_integral(100); break; case 2: divmod_integral(10); break; case 1: digit = integral; integral = 0; break; default: FMT_ASSERT(false, "invalid number of digits"); } --exp; auto remainder = (static_cast(integral) << -one.e) + fractional; result = handler.on_digit(static_cast('0' + digit), power_of_10_64(exp) << -one.e, remainder, error, exp, true); if (result != digits::more) return result; } while (exp > 0); // Generate digits for the fractional part. for (;;) { fractional *= 10; error *= 10; char digit = static_cast('0' + (fractional >> -one.e)); fractional &= one.f - 1; --exp; result = handler.on_digit(digit, one.f, fractional, error, exp, false); if (result != digits::more) return result; } } // The fixed precision digit handler. struct fixed_handler { char* buf; int size; int precision; int exp10; bool fixed; digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error, int& exp) { // Non-fixed formats require at least one digit and no precision adjustment. if (!fixed) return digits::more; // Adjust fixed precision by exponent because it is relative to decimal // point. precision += exp + exp10; // Check if precision is satisfied just by leading zeros, e.g. // format("{:.2f}", 0.001) gives "0.00" without generating any digits. if (precision > 0) return digits::more; if (precision < 0) return digits::done; auto dir = get_round_direction(divisor, remainder, error); if (dir == round_direction::unknown) return digits::error; buf[size++] = dir == round_direction::up ? '1' : '0'; return digits::done; } digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder, uint64_t error, int, bool integral) { FMT_ASSERT(remainder < divisor, ""); buf[size++] = digit; if (!integral && error >= remainder) return digits::error; if (size < precision) return digits::more; if (!integral) { // Check if error * 2 < divisor with overflow prevention. // The check is not needed for the integral part because error = 1 // and divisor > (1 << 32) there. if (error >= divisor || error >= divisor - error) return digits::error; } else { FMT_ASSERT(error == 1 && divisor > 2, ""); } auto dir = get_round_direction(divisor, remainder, error); if (dir != round_direction::up) return dir == round_direction::down ? digits::done : digits::error; ++buf[size - 1]; for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { buf[i] = '0'; ++buf[i - 1]; } if (buf[0] > '9') { buf[0] = '1'; if (fixed) buf[size++] = '0'; else ++exp10; } return digits::done; } }; // A 128-bit integer type used internally, struct uint128_wrapper { uint128_wrapper() = default; #if FMT_USE_INT128 uint128_t internal_; constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT : internal_{static_cast(low) | (static_cast(high) << 64)} {} constexpr uint128_wrapper(uint128_t u) : internal_{u} {} constexpr uint64_t high() const FMT_NOEXCEPT { return uint64_t(internal_ >> 64); } constexpr uint64_t low() const FMT_NOEXCEPT { return uint64_t(internal_); } uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT { internal_ += n; return *this; } #else uint64_t high_; uint64_t low_; constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT : high_{high}, low_{low} {} constexpr uint64_t high() const FMT_NOEXCEPT { return high_; } constexpr uint64_t low() const FMT_NOEXCEPT { return low_; } uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT { # if defined(_MSC_VER) && defined(_M_X64) unsigned char carry = _addcarry_u64(0, low_, n, &low_); _addcarry_u64(carry, high_, 0, &high_); return *this; # else uint64_t sum = low_ + n; high_ += (sum < low_ ? 1 : 0); low_ = sum; return *this; # endif } #endif }; // Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. namespace dragonbox { // Computes 128-bit result of multiplication of two 64-bit unsigned integers. inline uint128_wrapper umul128(uint64_t x, uint64_t y) FMT_NOEXCEPT { #if FMT_USE_INT128 return static_cast(x) * static_cast(y); #elif defined(_MSC_VER) && defined(_M_X64) uint128_wrapper result; result.low_ = _umul128(x, y, &result.high_); return result; #else const uint64_t mask = (uint64_t(1) << 32) - uint64_t(1); uint64_t a = x >> 32; uint64_t b = x & mask; uint64_t c = y >> 32; uint64_t d = y & mask; uint64_t ac = a * c; uint64_t bc = b * c; uint64_t ad = a * d; uint64_t bd = b * d; uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), (intermediate << 32) + (bd & mask)}; #endif } // Computes upper 64 bits of multiplication of two 64-bit unsigned integers. inline uint64_t umul128_upper64(uint64_t x, uint64_t y) FMT_NOEXCEPT { #if FMT_USE_INT128 auto p = static_cast(x) * static_cast(y); return static_cast(p >> 64); #elif defined(_MSC_VER) && defined(_M_X64) return __umulh(x, y); #else return umul128(x, y).high(); #endif } // Computes upper 64 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. inline uint64_t umul192_upper64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT { uint128_wrapper g0 = umul128(x, y.high()); g0 += umul128_upper64(x, y.low()); return g0.high(); } // Computes upper 32 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. inline uint32_t umul96_upper32(uint32_t x, uint64_t y) FMT_NOEXCEPT { return static_cast(umul128_upper64(x, y)); } // Computes middle 64 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. inline uint64_t umul192_middle64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT { uint64_t g01 = x * y.high(); uint64_t g10 = umul128_upper64(x, y.low()); return g01 + g10; } // Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT { return x * y; } // Computes floor(log10(pow(2, e))) for e in [-1700, 1700] using the method from // https://fmt.dev/papers/Grisu-Exact.pdf#page=5, section 3.4. inline int floor_log10_pow2(int e) FMT_NOEXCEPT { FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); const int shift = 22; return (e * static_cast(data::log10_2_significand >> (64 - shift))) >> shift; } // Various fast log computations. inline int floor_log2_pow10(int e) FMT_NOEXCEPT { FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); const uint64_t log2_10_integer_part = 3; const uint64_t log2_10_fractional_digits = 0x5269e12f346e2bf9; const int shift_amount = 19; return (e * static_cast( (log2_10_integer_part << shift_amount) | (log2_10_fractional_digits >> (64 - shift_amount)))) >> shift_amount; } inline int floor_log10_pow2_minus_log10_4_over_3(int e) FMT_NOEXCEPT { FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375; const int shift_amount = 22; return (e * static_cast(data::log10_2_significand >> (64 - shift_amount)) - static_cast(log10_4_over_3_fractional_digits >> (64 - shift_amount))) >> shift_amount; } // Returns true iff x is divisible by pow(2, exp). inline bool divisible_by_power_of_2(uint32_t x, int exp) FMT_NOEXCEPT { FMT_ASSERT(exp >= 1, ""); FMT_ASSERT(x != 0, ""); #ifdef FMT_BUILTIN_CTZ return FMT_BUILTIN_CTZ(x) >= exp; #else return exp < num_bits() && x == ((x >> exp) << exp); #endif } inline bool divisible_by_power_of_2(uint64_t x, int exp) FMT_NOEXCEPT { FMT_ASSERT(exp >= 1, ""); FMT_ASSERT(x != 0, ""); #ifdef FMT_BUILTIN_CTZLL return FMT_BUILTIN_CTZLL(x) >= exp; #else return exp < num_bits() && x == ((x >> exp) << exp); #endif } // Table entry type for divisibility test. template struct divtest_table_entry { T mod_inv; T max_quotient; }; // Returns true iff x is divisible by pow(5, exp). inline bool divisible_by_power_of_5(uint32_t x, int exp) FMT_NOEXCEPT { FMT_ASSERT(exp <= 10, "too large exponent"); static constexpr const divtest_table_entry divtest_table[] = { {0x00000001, 0xffffffff}, {0xcccccccd, 0x33333333}, {0xc28f5c29, 0x0a3d70a3}, {0x26e978d5, 0x020c49ba}, {0x3afb7e91, 0x0068db8b}, {0x0bcbe61d, 0x0014f8b5}, {0x68c26139, 0x000431bd}, {0xae8d46a5, 0x0000d6bf}, {0x22e90e21, 0x00002af3}, {0x3a2e9c6d, 0x00000897}, {0x3ed61f49, 0x000001b7}}; return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient; } inline bool divisible_by_power_of_5(uint64_t x, int exp) FMT_NOEXCEPT { FMT_ASSERT(exp <= 23, "too large exponent"); static constexpr const divtest_table_entry divtest_table[] = { {0x0000000000000001, 0xffffffffffffffff}, {0xcccccccccccccccd, 0x3333333333333333}, {0x8f5c28f5c28f5c29, 0x0a3d70a3d70a3d70}, {0x1cac083126e978d5, 0x020c49ba5e353f7c}, {0xd288ce703afb7e91, 0x0068db8bac710cb2}, {0x5d4e8fb00bcbe61d, 0x0014f8b588e368f0}, {0x790fb65668c26139, 0x000431bde82d7b63}, {0xe5032477ae8d46a5, 0x0000d6bf94d5e57a}, {0xc767074b22e90e21, 0x00002af31dc46118}, {0x8e47ce423a2e9c6d, 0x0000089705f4136b}, {0x4fa7f60d3ed61f49, 0x000001b7cdfd9d7b}, {0x0fee64690c913975, 0x00000057f5ff85e5}, {0x3662e0e1cf503eb1, 0x000000119799812d}, {0xa47a2cf9f6433fbd, 0x0000000384b84d09}, {0x54186f653140a659, 0x00000000b424dc35}, {0x7738164770402145, 0x0000000024075f3d}, {0xe4a4d1417cd9a041, 0x000000000734aca5}, {0xc75429d9e5c5200d, 0x000000000170ef54}, {0xc1773b91fac10669, 0x000000000049c977}, {0x26b172506559ce15, 0x00000000000ec1e4}, {0xd489e3a9addec2d1, 0x000000000002f394}, {0x90e860bb892c8d5d, 0x000000000000971d}, {0x502e79bf1b6f4f79, 0x0000000000001e39}, {0xdcd618596be30fe5, 0x000000000000060b}}; return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient; } // Replaces n by floor(n / pow(5, N)) returning true if and only if n is // divisible by pow(5, N). // Precondition: n <= 2 * pow(5, N + 1). template bool check_divisibility_and_divide_by_pow5(uint32_t& n) FMT_NOEXCEPT { static constexpr struct { uint32_t magic_number; int bits_for_comparison; uint32_t threshold; int shift_amount; } infos[] = {{0xcccd, 16, 0x3333, 18}, {0xa429, 8, 0x0a, 20}}; constexpr auto info = infos[N - 1]; n *= info.magic_number; const uint32_t comparison_mask = (1u << info.bits_for_comparison) - 1; bool result = (n & comparison_mask) <= info.threshold; n >>= info.shift_amount; return result; } // Computes floor(n / pow(10, N)) for small n and N. // Precondition: n <= pow(10, N + 1). template uint32_t small_division_by_pow10(uint32_t n) FMT_NOEXCEPT { static constexpr struct { uint32_t magic_number; int shift_amount; uint32_t divisor_times_10; } infos[] = {{0xcccd, 19, 100}, {0xa3d8, 22, 1000}}; constexpr auto info = infos[N - 1]; FMT_ASSERT(n <= info.divisor_times_10, "n is too large"); return n * info.magic_number >> info.shift_amount; } // Computes floor(n / 10^(kappa + 1)) (float) inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) FMT_NOEXCEPT { return n / float_info::big_divisor; } // Computes floor(n / 10^(kappa + 1)) (double) inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) FMT_NOEXCEPT { return umul128_upper64(n, 0x83126e978d4fdf3c) >> 9; } // Various subroutines using pow10 cache template struct cache_accessor; template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint64_t; static uint64_t get_cached_power(int k) FMT_NOEXCEPT { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); constexpr const uint64_t pow10_significands[] = { 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb, 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a, 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810, 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd, 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424, 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b, 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984, 0xa18f07d736b90be5, 0xc9f2c9cd04674ede, 0xfc6f7c4045812296, 0x9dc5ada82b70b59d, 0xc5371912364ce305, 0xf684df56c3e01bc6, 0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20, 0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd, 0x92efd1b8d0cf37be, 0xb7abc627050305ad, 0xe596b7b0c643c719, 0x8f7e32ce7bea5c6f, 0xb35dbf821ae4f38b, 0xe0352f62a19e306e}; return pow10_significands[k - float_info::min_k]; } static carrier_uint compute_mul(carrier_uint u, const cache_entry_type& cache) FMT_NOEXCEPT { return umul96_upper32(u, cache); } static uint32_t compute_delta(const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { return static_cast(cache >> (64 - 1 - beta_minus_1)); } static bool compute_mul_parity(carrier_uint two_f, const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { FMT_ASSERT(beta_minus_1 >= 1, ""); FMT_ASSERT(beta_minus_1 < 64, ""); return ((umul96_lower64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0; } static carrier_uint compute_left_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { return static_cast( (cache - (cache >> (float_info::significand_bits + 2))) >> (64 - float_info::significand_bits - 1 - beta_minus_1)); } static carrier_uint compute_right_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { return static_cast( (cache + (cache >> (float_info::significand_bits + 1))) >> (64 - float_info::significand_bits - 1 - beta_minus_1)); } static carrier_uint compute_round_up_for_shorter_interval_case( const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { return (static_cast( cache >> (64 - float_info::significand_bits - 2 - beta_minus_1)) + 1) / 2; } }; template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint128_wrapper; static uint128_wrapper get_cached_power(int k) FMT_NOEXCEPT { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); static constexpr const uint128_wrapper pow10_significands[] = { #if FMT_USE_FULL_CACHE_DRAGONBOX {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0x9faacf3df73609b1, 0x77b191618c54e9ad}, {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, {0x9becce62836ac577, 0x4ee367f9430aec33}, {0xc2e801fb244576d5, 0x229c41f793cda740}, {0xf3a20279ed56d48a, 0x6b43527578c11110}, {0x9845418c345644d6, 0x830a13896b78aaaa}, {0xbe5691ef416bd60c, 0x23cc986bc656d554}, {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, {0x91376c36d99995be, 0x23100809b9c21fa2}, {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, {0xdd95317f31c7fa1d, 0x40405643d711d584}, {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, {0xad1c8eab5ee43b66, 0xda3243650005eed0}, {0xd863b256369d4a40, 0x90bed43e40076a83}, {0x873e4f75e2224e68, 0x5a7744a6e804a292}, {0xa90de3535aaae202, 0x711515d0a205cb37}, {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, {0x8412d9991ed58091, 0xe858790afe9486c3}, {0xa5178fff668ae0b6, 0x626e974dbe39a873}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, {0xc987434744ac874e, 0xa327ffb266b56221}, {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, {0xf6019da07f549b2b, 0x7e2a53a146606a49}, {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, {0xc0314325637a1939, 0xfa911155fefb5309}, {0xf03d93eebc589f88, 0x793555ab7eba27cb}, {0x96267c7535b763b5, 0x4bc1558b2f3458df}, {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, {0x92a1958a7675175f, 0x0bfacd89ec191eca}, {0xb749faed14125d36, 0xcef980ec671f667c}, {0xe51c79a85916f484, 0x82b7e12780e7401b}, {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, {0xaecc49914078536d, 0x58fae9f773886e19}, {0xda7f5bf590966848, 0xaf39a475506a899f}, {0x888f99797a5e012d, 0x6d8406c952429604}, {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, {0xd0601d8efc57b08b, 0xf13b94daf124da27}, {0x823c12795db6ce57, 0x76c53d08d6b70859}, {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, {0xc21094364dfb5636, 0x985915fc12f542e5}, {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, {0xbd8430bd08277231, 0x50c6ff782a838354}, {0xece53cec4a314ebd, 0xa4f8bf5635246429}, {0x940f4613ae5ed136, 0x871b7795e136be9a}, {0xb913179899f68584, 0x28e2557b59846e40}, {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, {0xb4bca50b065abe63, 0x0fed077a756b53aa}, {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, {0x89e42caaf9491b60, 0xf41686c49db57245}, {0xac5d37d5b79b6239, 0x311c2875c522ced6}, {0xd77485cb25823ac7, 0x7d633293366b828c}, {0x86a8d39ef77164bc, 0xae5dff9c02033198}, {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, {0xd267caa862a12d66, 0xd072df63c324fd7c}, {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, {0xa46116538d0deb78, 0x52d9be85f074e609}, {0xcd795be870516656, 0x67902e276c921f8c}, {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, {0xef340a98172aace4, 0x86fb897116c87c35}, {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, {0xbae0a846d2195712, 0x8974836059cca10a}, {0xe998d258869facd7, 0x2bd1a438703fc94c}, {0x91ff83775423cc06, 0x7b6306a34627ddd0}, {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, {0x8e938662882af53e, 0x547eb47b7282ee9d}, {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, {0xae0b158b4738705e, 0x9624ab50b148d446}, {0xd98ddaee19068c76, 0x3badd624dd9b0958}, {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, {0xd47487cc8470652b, 0x7647c32000696720}, {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, {0xa5fb0a17c777cf09, 0xf468107100525891}, {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, {0x81ac1fe293d599bf, 0xc6f14cd848405531}, {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, {0xfd442e4688bd304a, 0x908f4a166d1da664}, {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, {0xf7549530e188c128, 0xd12bee59e68ef47d}, {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, {0xebdf661791d60f56, 0x111b495b3464ad22}, {0x936b9fcebb25c995, 0xcab10dd900beec35}, {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, {0xb3f4e093db73a093, 0x59ed216765690f57}, {0xe0f218b8d25088b8, 0x306869c13ec3532d}, {0x8c974f7383725573, 0x1e414218c73a13fc}, {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, {0x894bc396ce5da772, 0x6b8bba8c328eb784}, {0xab9eb47c81f5114f, 0x066ea92f3f326565}, {0xd686619ba27255a2, 0xc80a537b0efefebe}, {0x8613fd0145877585, 0xbd06742ce95f5f37}, {0xa798fc4196e952e7, 0x2c48113823b73705}, {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, {0x82ef85133de648c4, 0x9a984d73dbe722fc}, {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, {0xcc963fee10b7d1b3, 0x318df905079926a9}, {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, {0x9c1661a651213e2d, 0x06bea10ca65c084f}, {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, {0xbe89523386091465, 0xf6bbb397f1135824}, {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, {0xba121a4650e4ddeb, 0x92f34d62616ce414}, {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, {0x87625f056c7c4a8b, 0x11471cd764ad4973}, {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, {0xd389b47879823479, 0x4aff1d108d4ec2c4}, {0x843610cb4bf160cb, 0xcedf722a585139bb}, {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, {0xce947a3da6a9273e, 0x733d226229feea33}, {0x811ccc668829b887, 0x0806357d5a3f5260}, {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, {0xc5029163f384a931, 0x0a9e795e65d4df12}, {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, {0x964e858c91ba2655, 0x3a6a07f8d510f870}, {0xbbe226efb628afea, 0x890489f70a55368c}, {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, {0xe55990879ddcaabd, 0xcc420a6a101d0516}, {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, {0xb32df8e9f3546564, 0x47939822dc96abfa}, {0xdff9772470297ebd, 0x59787e2b93bc56f8}, {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, {0xaefae51477a06b03, 0xede622920b6b23f2}, {0xdab99e59958885c4, 0xe95fab368e45ecee}, {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, {0xd59944a37c0752a2, 0x4be76d3346f04960}, {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, {0x825ecc24c873782f, 0x8ed400668c0c28c9}, {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, {0xc24452da229b021b, 0xfbe85badce996169}, {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, {0xed246723473e3813, 0x290123e9aab23b69}, {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, {0x8d590723948a535f, 0x579c487e5a38ad0f}, {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, {0xdcdb1b2798182244, 0xf8e431456cf88e66}, {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, {0xa87fea27a539e9a5, 0x3f2398d747b36225}, {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, {0x83a3eeeef9153e89, 0x1953cf68300424ad}, {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, {0xcdb02555653131b6, 0x3792f412cb06794e}, {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, {0xc8de047564d20a8b, 0xf245825a5a445276}, {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, {0x9ced737bb6c4183d, 0x55464dd69685606c}, {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, {0xf53304714d9265df, 0xd53dd99f4b3066a9}, {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, {0xbf8fdb78849a5f96, 0xde98520472bdd034}, {0xef73d256a5c0f77c, 0x963e66858f6d4441}, {0x95a8637627989aad, 0xdde7001379a44aa9}, {0xbb127c53b17ec159, 0x5560c018580d5d53}, {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, {0x9226712162ab070d, 0xcab3961304ca70e9}, {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, {0xb267ed1940f1c61c, 0x55f038b237591ed4}, {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, {0xd9c7dced53c72255, 0x96e7bd358c904a22}, {0x881cea14545c7575, 0x7e50d64177da2e55}, {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, {0xcfb11ead453994ba, 0x67de18eda5814af3}, {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, {0xa2425ff75e14fc31, 0xa1258379a94d028e}, {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, {0x9e74d1b791e07e48, 0x775ea264cf55347e}, {0xc612062576589dda, 0x95364afe032a819e}, {0xf79687aed3eec551, 0x3a83ddbd83f52205}, {0x9abe14cd44753b52, 0xc4926a9672793543}, {0xc16d9a0095928a27, 0x75b7053c0f178294}, {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, {0xb877aa3236a4b449, 0x09befeb9fad487c3}, {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, {0xb424dc35095cd80f, 0x538484c19ef38c95}, {0xe12e13424bb40e13, 0x2865a5f206b06fba}, {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, {0xafebff0bcb24aafe, 0xf78f69a51539d749}, {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, {0x89705f4136b4a597, 0x31680a88f8953031}, {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, {0xd1b71758e219652b, 0xd3c36113404ea4a9}, {0x83126e978d4fdf3b, 0x645a1cac083126ea}, {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, {0xcccccccccccccccc, 0xcccccccccccccccd}, {0x8000000000000000, 0x0000000000000000}, {0xa000000000000000, 0x0000000000000000}, {0xc800000000000000, 0x0000000000000000}, {0xfa00000000000000, 0x0000000000000000}, {0x9c40000000000000, 0x0000000000000000}, {0xc350000000000000, 0x0000000000000000}, {0xf424000000000000, 0x0000000000000000}, {0x9896800000000000, 0x0000000000000000}, {0xbebc200000000000, 0x0000000000000000}, {0xee6b280000000000, 0x0000000000000000}, {0x9502f90000000000, 0x0000000000000000}, {0xba43b74000000000, 0x0000000000000000}, {0xe8d4a51000000000, 0x0000000000000000}, {0x9184e72a00000000, 0x0000000000000000}, {0xb5e620f480000000, 0x0000000000000000}, {0xe35fa931a0000000, 0x0000000000000000}, {0x8e1bc9bf04000000, 0x0000000000000000}, {0xb1a2bc2ec5000000, 0x0000000000000000}, {0xde0b6b3a76400000, 0x0000000000000000}, {0x8ac7230489e80000, 0x0000000000000000}, {0xad78ebc5ac620000, 0x0000000000000000}, {0xd8d726b7177a8000, 0x0000000000000000}, {0x878678326eac9000, 0x0000000000000000}, {0xa968163f0a57b400, 0x0000000000000000}, {0xd3c21bcecceda100, 0x0000000000000000}, {0x84595161401484a0, 0x0000000000000000}, {0xa56fa5b99019a5c8, 0x0000000000000000}, {0xcecb8f27f4200f3a, 0x0000000000000000}, {0x813f3978f8940984, 0x4000000000000000}, {0xa18f07d736b90be5, 0x5000000000000000}, {0xc9f2c9cd04674ede, 0xa400000000000000}, {0xfc6f7c4045812296, 0x4d00000000000000}, {0x9dc5ada82b70b59d, 0xf020000000000000}, {0xc5371912364ce305, 0x6c28000000000000}, {0xf684df56c3e01bc6, 0xc732000000000000}, {0x9a130b963a6c115c, 0x3c7f400000000000}, {0xc097ce7bc90715b3, 0x4b9f100000000000}, {0xf0bdc21abb48db20, 0x1e86d40000000000}, {0x96769950b50d88f4, 0x1314448000000000}, {0xbc143fa4e250eb31, 0x17d955a000000000}, {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, {0xb7abc627050305ad, 0xf14a3d9e40000000}, {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, {0xe0352f62a19e306e, 0xd50b2037ad200000}, {0x8c213d9da502de45, 0x4526f422cc340000}, {0xaf298d050e4395d6, 0x9670b12b7f410000}, {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, {0xab0e93b6efee0053, 0x8eea0d047a457a00}, {0xd5d238a4abe98068, 0x72a4904598d6d880}, {0x85a36366eb71f041, 0x47a6da2b7f864750}, {0xa70c3c40a64e6c51, 0x999090b65f67d924}, {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, {0x82818f1281ed449f, 0xbff8f10e7a8921a4}, {0xa321f2d7226895c7, 0xaff72d52192b6a0d}, {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490}, {0xfee50b7025c36a08, 0x02f236d04753d5b4}, {0x9f4f2726179a2245, 0x01d762422c946590}, {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5}, {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2}, {0x9b934c3b330c8577, 0x63cc55f49f88eb2f}, {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb}, {0xf316271c7fc3908a, 0x8bef464e3945ef7a}, {0x97edd871cfda3a56, 0x97758bf0e3cbb5ac}, {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317}, {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd}, {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a}, {0xb975d6b6ee39e436, 0xb3e2fd538e122b44}, {0xe7d34c64a9c85d44, 0x60dbbca87196b616}, {0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd}, {0xb51d13aea4a488dd, 0x6babab6398bdbe41}, {0xe264589a4dcdab14, 0xc696963c7eed2dd1}, {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2}, {0xb0de65388cc8ada8, 0x3b25a55f43294bcb}, {0xdd15fe86affad912, 0x49ef0eb713f39ebe}, {0x8a2dbf142dfcc7ab, 0x6e3569326c784337}, {0xacb92ed9397bf996, 0x49c2c37f07965404}, {0xd7e77a8f87daf7fb, 0xdc33745ec97be906}, {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3}, {0xa8acd7c0222311bc, 0xc40832ea0d68ce0c}, {0xd2d80db02aabd62b, 0xf50a3fa490c30190}, {0x83c7088e1aab65db, 0x792667c6da79e0fa}, {0xa4b8cab1a1563f52, 0x577001b891185938}, {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86}, {0x80b05e5ac60b6178, 0x544f8158315b05b4}, {0xa0dc75f1778e39d6, 0x696361ae3db1c721}, {0xc913936dd571c84c, 0x03bc3a19cd1e38e9}, {0xfb5878494ace3a5f, 0x04ab48a04065c723}, {0x9d174b2dcec0e47b, 0x62eb0d64283f9c76}, {0xc45d1df942711d9a, 0x3ba5d0bd324f8394}, {0xf5746577930d6500, 0xca8f44ec7ee36479}, {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb}, {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e}, {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e}, {0x95d04aee3b80ece5, 0xbba1f1d158724a12}, {0xbb445da9ca61281f, 0x2a8a6e45ae8edc97}, {0xea1575143cf97226, 0xf52d09d71a3293bd}, {0x924d692ca61be758, 0x593c2626705f9c56}, {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c}, {0xe498f455c38b997a, 0x0b6dfb9c0f956447}, {0x8edf98b59a373fec, 0x4724bd4189bd5eac}, {0xb2977ee300c50fe7, 0x58edec91ec2cb657}, {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed}, {0x8b865b215899f46c, 0xbd79e0d20082ee74}, {0xae67f1e9aec07187, 0xecd8590680a3aa11}, {0xda01ee641a708de9, 0xe80e6f4820cc9495}, {0x884134fe908658b2, 0x3109058d147fdcdd}, {0xaa51823e34a7eede, 0xbd4b46f0599fd415}, {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a}, {0x850fadc09923329e, 0x03e2cf6bc604ddb0}, {0xa6539930bf6bff45, 0x84db8346b786151c}, {0xcfe87f7cef46ff16, 0xe612641865679a63}, {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e}, {0xa26da3999aef7749, 0xe3be5e330f38f09d}, {0xcb090c8001ab551c, 0x5cadf5bfd3072cc5}, {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6}, {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa}, {0xc646d63501a1511d, 0xb281e1fd541501b8}, {0xf7d88bc24209a565, 0x1f225a7ca91a4226}, {0x9ae757596946075f, 0x3375788de9b06958}, {0xc1a12d2fc3978937, 0x0052d6b1641c83ae}, {0xf209787bb47d6b84, 0xc0678c5dbd23a49a}, {0x9745eb4d50ce6332, 0xf840b7ba963646e0}, {0xbd176620a501fbff, 0xb650e5a93bc3d898}, {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe}, {0x93ba47c980e98cdf, 0xc66f336c36b10137}, {0xb8a8d9bbe123f017, 0xb80b0047445d4184}, {0xe6d3102ad96cec1d, 0xa60dc059157491e5}, {0x9043ea1ac7e41392, 0x87c89837ad68db2f}, {0xb454e4a179dd1877, 0x29babe4598c311fb}, {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a}, {0x8ce2529e2734bb1d, 0x1899e4a65f58660c}, {0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f}, {0xdc21a1171d42645d, 0x76707543f4fa1f73}, {0x899504ae72497eba, 0x6a06494a791c53a8}, {0xabfa45da0edbde69, 0x0487db9d17636892}, {0xd6f8d7509292d603, 0x45a9d2845d3c42b6}, {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2}, {0xa7f26836f282b732, 0x8e6cac7768d7141e}, {0xd1ef0244af2364ff, 0x3207d795430cd926}, {0x8335616aed761f1f, 0x7f44e6bd49e807b8}, {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6}, {0xcd036837130890a1, 0x36dba887c37a8c0f}, {0x802221226be55a64, 0xc2494954da2c9789}, {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c}, {0xc83553c5c8965d3d, 0x6f92829494e5acc7}, {0xfa42a8b73abbf48c, 0xcb772339ba1f17f9}, {0x9c69a97284b578d7, 0xff2a760414536efb}, {0xc38413cf25e2d70d, 0xfef5138519684aba}, {0xf46518c2ef5b8cd1, 0x7eb258665fc25d69}, {0x98bf2f79d5993802, 0xef2f773ffbd97a61}, {0xbeeefb584aff8603, 0xaafb550ffacfd8fa}, {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38}, {0x952ab45cfa97a0b2, 0xdd945a747bf26183}, {0xba756174393d88df, 0x94f971119aeef9e4}, {0xe912b9d1478ceb17, 0x7a37cd5601aab85d}, {0x91abb422ccb812ee, 0xac62e055c10ab33a}, {0xb616a12b7fe617aa, 0x577b986b314d6009}, {0xe39c49765fdf9d94, 0xed5a7e85fda0b80b}, {0x8e41ade9fbebc27d, 0x14588f13be847307}, {0xb1d219647ae6b31c, 0x596eb2d8ae258fc8}, {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb}, {0x8aec23d680043bee, 0x25de7bb9480d5854}, {0xada72ccc20054ae9, 0xaf561aa79a10ae6a}, {0xd910f7ff28069da4, 0x1b2ba1518094da04}, {0x87aa9aff79042286, 0x90fb44d2f05d0842}, {0xa99541bf57452b28, 0x353a1607ac744a53}, {0xd3fa922f2d1675f2, 0x42889b8997915ce8}, {0x847c9b5d7c2e09b7, 0x69956135febada11}, {0xa59bc234db398c25, 0x43fab9837e699095}, {0xcf02b2c21207ef2e, 0x94f967e45e03f4bb}, {0x8161afb94b44f57d, 0x1d1be0eebac278f5}, {0xa1ba1ba79e1632dc, 0x6462d92a69731732}, {0xca28a291859bbf93, 0x7d7b8f7503cfdcfe}, {0xfcb2cb35e702af78, 0x5cda735244c3d43e}, {0x9defbf01b061adab, 0x3a0888136afa64a7}, {0xc56baec21c7a1916, 0x088aaa1845b8fdd0}, {0xf6c69a72a3989f5b, 0x8aad549e57273d45}, {0x9a3c2087a63f6399, 0x36ac54e2f678864b}, {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd}, {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5}, {0x969eb7c47859e743, 0x9f644ae5a4b1b325}, {0xbc4665b596706114, 0x873d5d9f0dde1fee}, {0xeb57ff22fc0c7959, 0xa90cb506d155a7ea}, {0x9316ff75dd87cbd8, 0x09a7f12442d588f2}, {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb2f}, {0xe5d3ef282a242e81, 0x8f1668c8a86da5fa}, {0x8fa475791a569d10, 0xf96e017d694487bc}, {0xb38d92d760ec4455, 0x37c981dcc395a9ac}, {0xe070f78d3927556a, 0x85bbe253f47b1417}, {0x8c469ab843b89562, 0x93956d7478ccec8e}, {0xaf58416654a6babb, 0x387ac8d1970027b2}, {0xdb2e51bfe9d0696a, 0x06997b05fcc0319e}, {0x88fcf317f22241e2, 0x441fece3bdf81f03}, {0xab3c2fddeeaad25a, 0xd527e81cad7626c3}, {0xd60b3bd56a5586f1, 0x8a71e223d8d3b074}, {0x85c7056562757456, 0xf6872d5667844e49}, {0xa738c6bebb12d16c, 0xb428f8ac016561db}, {0xd106f86e69d785c7, 0xe13336d701beba52}, {0x82a45b450226b39c, 0xecc0024661173473}, {0xa34d721642b06084, 0x27f002d7f95d0190}, {0xcc20ce9bd35c78a5, 0x31ec038df7b441f4}, {0xff290242c83396ce, 0x7e67047175a15271}, {0x9f79a169bd203e41, 0x0f0062c6e984d386}, {0xc75809c42c684dd1, 0x52c07b78a3e60868}, {0xf92e0c3537826145, 0xa7709a56ccdf8a82}, {0x9bbcc7a142b17ccb, 0x88a66076400bb691}, {0xc2abf989935ddbfe, 0x6acff893d00ea435}, {0xf356f7ebf83552fe, 0x0583f6b8c4124d43}, {0x98165af37b2153de, 0xc3727a337a8b704a}, {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c}, {0xeda2ee1c7064130c, 0x1162def06f79df73}, {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8}, {0xb9a74a0637ce2ee1, 0x6d953e2bd7173692}, {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437}, {0x910ab1d4db9914a0, 0x1d9c9892400a22a2}, {0xb54d5e4a127f59c8, 0x2503beb6d00cab4b}, {0xe2a0b5dc971f303a, 0x2e44ae64840fd61d}, {0x8da471a9de737e24, 0x5ceaecfed289e5d2}, {0xb10d8e1456105dad, 0x7425a83e872c5f47}, {0xdd50f1996b947518, 0xd12f124e28f77719}, {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f}, {0xace73cbfdc0bfb7b, 0x636cc64d1001550b}, {0xd8210befd30efa5a, 0x3c47f7e05401aa4e}, {0x8714a775e3e95c78, 0x65acfaec34810a71}, {0xa8d9d1535ce3b396, 0x7f1839a741a14d0d}, {0xd31045a8341ca07c, 0x1ede48111209a050}, {0x83ea2b892091e44d, 0x934aed0aab460432}, {0xa4e4b66b68b65d60, 0xf81da84d5617853f}, {0xce1de40642e3f4b9, 0x36251260ab9d668e}, {0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019}, {0xa1075a24e4421730, 0xb24cf65b8612f81f}, {0xc94930ae1d529cfc, 0xdee033f26797b627}, {0xfb9b7cd9a4a7443c, 0x169840ef017da3b1}, {0x9d412e0806e88aa5, 0x8e1f289560ee864e}, {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2}, {0xf5b5d7ec8acb58a2, 0xae10af696774b1db}, {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29}, {0xbff610b0cc6edd3f, 0x17fd090a58d32af3}, {0xeff394dcff8a948e, 0xddfc4b4cef07f5b0}, {0x95f83d0a1fb69cd9, 0x4abdaf101564f98e}, {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1}, {0xea53df5fd18d5513, 0x84c86189216dc5ed}, {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4}, {0xb7118682dbb66a77, 0x3fbc8c33221dc2a1}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334a}, {0x8f05b1163ba6832d, 0x29cb4d87f2a7400e}, {0xb2c71d5bca9023f8, 0x743e20e9ef511012}, {0xdf78e4b2bd342cf6, 0x914da9246b255416}, {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e}, {0xae9672aba3d0c320, 0xa184ac2473b529b1}, {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e}, {0x8865899617fb1871, 0x7e2fa67c7a658892}, {0xaa7eebfb9df9de8d, 0xddbb901b98feeab7}, {0xd51ea6fa85785631, 0x552a74227f3ea565}, {0x8533285c936b35de, 0xd53a88958f87275f}, {0xa67ff273b8460356, 0x8a892abaf368f137}, {0xd01fef10a657842c, 0x2d2b7569b0432d85}, {0x8213f56a67f6b29b, 0x9c3b29620e29fc73}, {0xa298f2c501f45f42, 0x8349f3ba91b47b8f}, {0xcb3f2f7642717713, 0x241c70a936219a73}, {0xfe0efb53d30dd4d7, 0xed238cd383aa0110}, {0x9ec95d1463e8a506, 0xf4363804324a40aa}, {0xc67bb4597ce2ce48, 0xb143c6053edcd0d5}, {0xf81aa16fdc1b81da, 0xdd94b7868e94050a}, {0x9b10a4e5e9913128, 0xca7cf2b4191c8326}, {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0}, {0xf24a01a73cf2dccf, 0xbc633b39673c8cec}, {0x976e41088617ca01, 0xd5be0503e085d813}, {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18}, {0xec9c459d51852ba2, 0xddf8e7d60ed1219e}, {0x93e1ab8252f33b45, 0xcabb90e5c942b503}, {0xb8da1662e7b00a17, 0x3d6a751f3b936243}, {0xe7109bfba19c0c9d, 0x0cc512670a783ad4}, {0x906a617d450187e2, 0x27fb2b80668b24c5}, {0xb484f9dc9641e9da, 0xb1f9f660802dedf6}, {0xe1a63853bbd26451, 0x5e7873f8a0396973}, {0x8d07e33455637eb2, 0xdb0b487b6423e1e8}, {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62}, {0xdc5c5301c56b75f7, 0x7641a140cc7810fb}, {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d}, {0xac2820d9623bf429, 0x546345fa9fbdcd44}, {0xd732290fbacaf133, 0xa97c177947ad4095}, {0x867f59a9d4bed6c0, 0x49ed8eabcccc485d}, {0xa81f301449ee8c70, 0x5c68f256bfff5a74}, {0xd226fc195c6a2f8c, 0x73832eec6fff3111}, {0x83585d8fd9c25db7, 0xc831fd53c5ff7eab}, {0xa42e74f3d032f525, 0xba3e7ca8b77f5e55}, {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb}, {0x80444b5e7aa7cf85, 0x7980d163cf5b81b3}, {0xa0555e361951c366, 0xd7e105bcc332621f}, {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7}, {0xfa856334878fc150, 0xb14f98f6f0feb951}, {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3}, {0xc3b8358109e84f07, 0x0a862f80ec4700c8}, {0xf4a642e14c6262c8, 0xcd27bb612758c0fa}, {0x98e7e9cccfbd7dbd, 0x8038d51cb897789c}, {0xbf21e44003acdd2c, 0xe0470a63e6bd56c3}, {0xeeea5d5004981478, 0x1858ccfce06cac74}, {0x95527a5202df0ccb, 0x0f37801e0c43ebc8}, {0xbaa718e68396cffd, 0xd30560258f54e6ba}, {0xe950df20247c83fd, 0x47c6b82ef32a2069}, {0x91d28b7416cdd27e, 0x4cdc331d57fa5441}, {0xb6472e511c81471d, 0xe0133fe4adf8e952}, {0xe3d8f9e563a198e5, 0x58180fddd97723a6}, {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648}, {0xb201833b35d63f73, 0x2cd2cc6551e513da}, {0xde81e40a034bcf4f, 0xf8077f7ea65e58d1}, {0x8b112e86420f6191, 0xfb04afaf27faf782}, {0xadd57a27d29339f6, 0x79c5db9af1f9b563}, {0xd94ad8b1c7380874, 0x18375281ae7822bc}, {0x87cec76f1c830548, 0x8f2293910d0b15b5}, {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb22}, {0xd433179d9c8cb841, 0x5fa60692a46151eb}, {0x849feec281d7f328, 0xdbc7c41ba6bcd333}, {0xa5c7ea73224deff3, 0x12b9b522906c0800}, {0xcf39e50feae16bef, 0xd768226b34870a00}, {0x81842f29f2cce375, 0xe6a1158300d46640}, {0xa1e53af46f801c53, 0x60495ae3c1097fd0}, {0xca5e89b18b602368, 0x385bb19cb14bdfc4}, {0xfcf62c1dee382c42, 0x46729e03dd9ed7b5}, {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d1}, {0xc5a05277621be293, 0xc7098b7305241885}, { 0xf70867153aa2db38, 0xb8cbee4fc66d1ea7 } #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, {0x86a8d39ef77164bc, 0xae5dff9c02033198}, {0xd98ddaee19068c76, 0x3badd624dd9b0958}, {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, {0xe55990879ddcaabd, 0xcc420a6a101d0516}, {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, {0x95a8637627989aad, 0xdde7001379a44aa9}, {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, {0xc350000000000000, 0x0000000000000000}, {0x9dc5ada82b70b59d, 0xf020000000000000}, {0xfee50b7025c36a08, 0x02f236d04753d5b4}, {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86}, {0xa6539930bf6bff45, 0x84db8346b786151c}, {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2}, {0xd910f7ff28069da4, 0x1b2ba1518094da04}, {0xaf58416654a6babb, 0x387ac8d1970027b2}, {0x8da471a9de737e24, 0x5ceaecfed289e5d2}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334a}, {0xb8da1662e7b00a17, 0x3d6a751f3b936243}, { 0x95527a5202df0ccb, 0x0f37801e0c43ebc8 } #endif }; #if FMT_USE_FULL_CACHE_DRAGONBOX return pow10_significands[k - float_info::min_k]; #else static constexpr const uint64_t powers_of_5_64[] = { 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd, 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9, 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5, 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631, 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; static constexpr const uint32_t pow10_recovery_errors[] = { 0x50001400, 0x54044100, 0x54014555, 0x55954415, 0x54115555, 0x00000001, 0x50000000, 0x00104000, 0x54010004, 0x05004001, 0x55555544, 0x41545555, 0x54040551, 0x15445545, 0x51555514, 0x10000015, 0x00101100, 0x01100015, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04450514, 0x45414110, 0x55555145, 0x50544050, 0x15040155, 0x11054140, 0x50111514, 0x11451454, 0x00400541, 0x00000000, 0x55555450, 0x10056551, 0x10054011, 0x55551014, 0x69514555, 0x05151109, 0x00155555}; static const int compression_ratio = 27; // Compute base index. int cache_index = (k - float_info::min_k) / compression_ratio; int kb = cache_index * compression_ratio + float_info::min_k; int offset = k - kb; // Get base cache. uint128_wrapper base_cache = pow10_significands[cache_index]; if (offset == 0) return base_cache; // Compute the required amount of bit-shift. int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset; FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected"); // Try to recover the real cache. uint64_t pow5 = powers_of_5_64[offset]; uint128_wrapper recovered_cache = umul128(base_cache.high(), pow5); uint128_wrapper middle_low = umul128(base_cache.low() - (kb < 0 ? 1u : 0u), pow5); recovered_cache += middle_low.high(); uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); recovered_cache = uint128_wrapper{(recovered_cache.low() >> alpha) | high_to_middle, ((middle_low.low() >> alpha) | middle_to_low)}; if (kb < 0) recovered_cache += 1; // Get error. int error_idx = (k - float_info::min_k) / 16; uint32_t error = (pow10_recovery_errors[error_idx] >> ((k - float_info::min_k) % 16) * 2) & 0x3; // Add the error back. FMT_ASSERT(recovered_cache.low() + error >= recovered_cache.low(), ""); return {recovered_cache.high(), recovered_cache.low() + error}; #endif } static carrier_uint compute_mul(carrier_uint u, const cache_entry_type& cache) FMT_NOEXCEPT { return umul192_upper64(u, cache); } static uint32_t compute_delta(cache_entry_type const& cache, int beta_minus_1) FMT_NOEXCEPT { return static_cast(cache.high() >> (64 - 1 - beta_minus_1)); } static bool compute_mul_parity(carrier_uint two_f, const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { FMT_ASSERT(beta_minus_1 >= 1, ""); FMT_ASSERT(beta_minus_1 < 64, ""); return ((umul192_middle64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0; } static carrier_uint compute_left_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { return (cache.high() - (cache.high() >> (float_info::significand_bits + 2))) >> (64 - float_info::significand_bits - 1 - beta_minus_1); } static carrier_uint compute_right_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { return (cache.high() + (cache.high() >> (float_info::significand_bits + 1))) >> (64 - float_info::significand_bits - 1 - beta_minus_1); } static carrier_uint compute_round_up_for_shorter_interval_case( const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { return ((cache.high() >> (64 - float_info::significand_bits - 2 - beta_minus_1)) + 1) / 2; } }; // Various integer checks template bool is_left_endpoint_integer_shorter_interval(int exponent) FMT_NOEXCEPT { return exponent >= float_info< T>::case_shorter_interval_left_endpoint_lower_threshold && exponent <= float_info::case_shorter_interval_left_endpoint_upper_threshold; } template bool is_endpoint_integer(typename float_info::carrier_uint two_f, int exponent, int minus_k) FMT_NOEXCEPT { if (exponent < float_info::case_fc_pm_half_lower_threshold) return false; // For k >= 0. if (exponent <= float_info::case_fc_pm_half_upper_threshold) return true; // For k < 0. if (exponent > float_info::divisibility_check_by_5_threshold) return false; return divisible_by_power_of_5(two_f, minus_k); } template bool is_center_integer(typename float_info::carrier_uint two_f, int exponent, int minus_k) FMT_NOEXCEPT { // Exponent for 5 is negative. if (exponent > float_info::divisibility_check_by_5_threshold) return false; if (exponent > float_info::case_fc_upper_threshold) return divisible_by_power_of_5(two_f, minus_k); // Both exponents are nonnegative. if (exponent >= float_info::case_fc_lower_threshold) return true; // Exponent for 2 is negative. return divisible_by_power_of_2(two_f, minus_k - exponent + 1); } // Remove trailing zeros from n and return the number of zeros removed (float) FMT_INLINE int remove_trailing_zeros(uint32_t& n) FMT_NOEXCEPT { #ifdef FMT_BUILTIN_CTZ int t = FMT_BUILTIN_CTZ(n); #else int t = ctz(n); #endif if (t > float_info::max_trailing_zeros) t = float_info::max_trailing_zeros; const uint32_t mod_inv1 = 0xcccccccd; const uint32_t max_quotient1 = 0x33333333; const uint32_t mod_inv2 = 0xc28f5c29; const uint32_t max_quotient2 = 0x0a3d70a3; int s = 0; for (; s < t - 1; s += 2) { if (n * mod_inv2 > max_quotient2) break; n *= mod_inv2; } if (s < t && n * mod_inv1 <= max_quotient1) { n *= mod_inv1; ++s; } n >>= s; return s; } // Removes trailing zeros and returns the number of zeros removed (double) FMT_INLINE int remove_trailing_zeros(uint64_t& n) FMT_NOEXCEPT { #ifdef FMT_BUILTIN_CTZLL int t = FMT_BUILTIN_CTZLL(n); #else int t = ctzll(n); #endif if (t > float_info::max_trailing_zeros) t = float_info::max_trailing_zeros; // Divide by 10^8 and reduce to 32-bits // Since ret_value.significand <= (2^64 - 1) / 1000 < 10^17, // both of the quotient and the r should fit in 32-bits const uint32_t mod_inv1 = 0xcccccccd; const uint32_t max_quotient1 = 0x33333333; const uint64_t mod_inv8 = 0xc767074b22e90e21; const uint64_t max_quotient8 = 0x00002af31dc46118; // If the number is divisible by 1'0000'0000, work with the quotient if (t >= 8) { auto quotient_candidate = n * mod_inv8; if (quotient_candidate <= max_quotient8) { auto quotient = static_cast(quotient_candidate >> 8); int s = 8; for (; s < t; ++s) { if (quotient * mod_inv1 > max_quotient1) break; quotient *= mod_inv1; } quotient >>= (s - 8); n = quotient; return s; } } // Otherwise, work with the remainder auto quotient = static_cast(n / 100000000); auto remainder = static_cast(n - 100000000 * quotient); if (t == 0 || remainder * mod_inv1 > max_quotient1) { return 0; } remainder *= mod_inv1; if (t == 1 || remainder * mod_inv1 > max_quotient1) { n = (remainder >> 1) + quotient * 10000000ull; return 1; } remainder *= mod_inv1; if (t == 2 || remainder * mod_inv1 > max_quotient1) { n = (remainder >> 2) + quotient * 1000000ull; return 2; } remainder *= mod_inv1; if (t == 3 || remainder * mod_inv1 > max_quotient1) { n = (remainder >> 3) + quotient * 100000ull; return 3; } remainder *= mod_inv1; if (t == 4 || remainder * mod_inv1 > max_quotient1) { n = (remainder >> 4) + quotient * 10000ull; return 4; } remainder *= mod_inv1; if (t == 5 || remainder * mod_inv1 > max_quotient1) { n = (remainder >> 5) + quotient * 1000ull; return 5; } remainder *= mod_inv1; if (t == 6 || remainder * mod_inv1 > max_quotient1) { n = (remainder >> 6) + quotient * 100ull; return 6; } remainder *= mod_inv1; n = (remainder >> 7) + quotient * 10ull; return 7; } // The main algorithm for shorter interval case template FMT_INLINE decimal_fp shorter_interval_case(int exponent) FMT_NOEXCEPT { decimal_fp ret_value; // Compute k and beta const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k); // Compute xi and zi using cache_entry_type = typename cache_accessor::cache_entry_type; const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); auto xi = cache_accessor::compute_left_endpoint_for_shorter_interval_case( cache, beta_minus_1); auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( cache, beta_minus_1); // If the left endpoint is not an integer, increase it if (!is_left_endpoint_integer_shorter_interval(exponent)) ++xi; // Try bigger divisor ret_value.significand = zi / 10; // If succeed, remove trailing zeros if necessary and return if (ret_value.significand * 10 >= xi) { ret_value.exponent = minus_k + 1; ret_value.exponent += remove_trailing_zeros(ret_value.significand); return ret_value; } // Otherwise, compute the round-up of y ret_value.significand = cache_accessor::compute_round_up_for_shorter_interval_case( cache, beta_minus_1); ret_value.exponent = minus_k; // When tie occurs, choose one of them according to the rule if (exponent >= float_info::shorter_interval_tie_lower_threshold && exponent <= float_info::shorter_interval_tie_upper_threshold) { ret_value.significand = ret_value.significand % 2 == 0 ? ret_value.significand : ret_value.significand - 1; } else if (ret_value.significand < xi) { ++ret_value.significand; } return ret_value; } template decimal_fp to_decimal(T x) FMT_NOEXCEPT { // Step 1: integer promotion & Schubfach multiplier calculation. using carrier_uint = typename float_info::carrier_uint; using cache_entry_type = typename cache_accessor::cache_entry_type; auto br = bit_cast(x); // Extract significand bits and exponent bits. const carrier_uint significand_mask = (static_cast(1) << float_info::significand_bits) - 1; carrier_uint significand = (br & significand_mask); int exponent = static_cast((br & exponent_mask()) >> float_info::significand_bits); if (exponent != 0) { // Check if normal. exponent += float_info::exponent_bias - float_info::significand_bits; // Shorter interval case; proceed like Schubfach. if (significand == 0) return shorter_interval_case(exponent); significand |= (static_cast(1) << float_info::significand_bits); } else { // Subnormal case; the interval is always regular. if (significand == 0) return {0, 0}; exponent = float_info::min_exponent - float_info::significand_bits; } const bool include_left_endpoint = (significand % 2 == 0); const bool include_right_endpoint = include_left_endpoint; // Compute k and beta. const int minus_k = floor_log10_pow2(exponent) - float_info::kappa; const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k); // Compute zi and deltai // 10^kappa <= deltai < 10^(kappa + 1) const uint32_t deltai = cache_accessor::compute_delta(cache, beta_minus_1); const carrier_uint two_fc = significand << 1; const carrier_uint two_fr = two_fc | 1; const carrier_uint zi = cache_accessor::compute_mul(two_fr << beta_minus_1, cache); // Step 2: Try larger divisor; remove trailing zeros if necessary // Using an upper bound on zi, we might be able to optimize the division // better than the compiler; we are computing zi / big_divisor here decimal_fp ret_value; ret_value.significand = divide_by_10_to_kappa_plus_1(zi); uint32_t r = static_cast(zi - float_info::big_divisor * ret_value.significand); if (r > deltai) { goto small_divisor_case_label; } else if (r < deltai) { // Exclude the right endpoint if necessary if (r == 0 && !include_right_endpoint && is_endpoint_integer(two_fr, exponent, minus_k)) { --ret_value.significand; r = float_info::big_divisor; goto small_divisor_case_label; } } else { // r == deltai; compare fractional parts // Check conditions in the order different from the paper // to take advantage of short-circuiting const carrier_uint two_fl = two_fc - 1; if ((!include_left_endpoint || !is_endpoint_integer(two_fl, exponent, minus_k)) && !cache_accessor::compute_mul_parity(two_fl, cache, beta_minus_1)) { goto small_divisor_case_label; } } ret_value.exponent = minus_k + float_info::kappa + 1; // We may need to remove trailing zeros ret_value.exponent += remove_trailing_zeros(ret_value.significand); return ret_value; // Step 3: Find the significand with the smaller divisor small_divisor_case_label: ret_value.significand *= 10; ret_value.exponent = minus_k + float_info::kappa; const uint32_t mask = (1u << float_info::kappa) - 1; auto dist = r - (deltai / 2) + (float_info::small_divisor / 2); // Is dist divisible by 2^kappa? if ((dist & mask) == 0) { const bool approx_y_parity = ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; dist >>= float_info::kappa; // Is dist divisible by 5^kappa? if (check_divisibility_and_divide_by_pow5::kappa>(dist)) { ret_value.significand += dist; // Check z^(f) >= epsilon^(f) // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f) // Since there are only 2 possibilities, we only need to care about the // parity. Also, zi and r should have the same parity since the divisor // is an even number if (cache_accessor::compute_mul_parity(two_fc, cache, beta_minus_1) != approx_y_parity) { --ret_value.significand; } else { // If z^(f) >= epsilon^(f), we might have a tie // when z^(f) == epsilon^(f), or equivalently, when y is an integer if (is_center_integer(two_fc, exponent, minus_k)) { ret_value.significand = ret_value.significand % 2 == 0 ? ret_value.significand : ret_value.significand - 1; } } } // Is dist not divisible by 5^kappa? else { ret_value.significand += dist; } } // Is dist not divisible by 2^kappa? else { // Since we know dist is small, we might be able to optimize the division // better than the compiler; we are computing dist / small_divisor here ret_value.significand += small_division_by_pow10::kappa>(dist); } return ret_value; } } // namespace dragonbox // Formats value using a variation of the Fixed-Precision Positive // Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // https://fmt.dev/papers/p372-steele.pdf. template void fallback_format(Double d, int num_digits, bool binary32, buffer& buf, int& exp10) { bigint numerator; // 2 * R in (FPP)^2. bigint denominator; // 2 * S in (FPP)^2. // lower and upper are differences between value and corresponding boundaries. bigint lower; // (M^- in (FPP)^2). bigint upper_store; // upper's value if different from lower. bigint* upper = nullptr; // (M^+ in (FPP)^2). fp value; // Shift numerator and denominator by an extra bit or two (if lower boundary // is closer) to make lower and upper integers. This eliminates multiplication // by 2 during later computations. const bool is_predecessor_closer = binary32 ? value.assign(static_cast(d)) : value.assign(d); int shift = is_predecessor_closer ? 2 : 1; uint64_t significand = value.f << shift; if (value.e >= 0) { numerator.assign(significand); numerator <<= value.e; lower.assign(1); lower <<= value.e; if (shift != 1) { upper_store.assign(1); upper_store <<= value.e + 1; upper = &upper_store; } denominator.assign_pow10(exp10); denominator <<= shift; } else if (exp10 < 0) { numerator.assign_pow10(-exp10); lower.assign(numerator); if (shift != 1) { upper_store.assign(numerator); upper_store <<= 1; upper = &upper_store; } numerator *= significand; denominator.assign(1); denominator <<= shift - value.e; } else { numerator.assign(significand); denominator.assign_pow10(exp10); denominator <<= shift - value.e; lower.assign(1); if (shift != 1) { upper_store.assign(1ULL << 1); upper = &upper_store; } } // Invariant: value == (numerator / denominator) * pow(10, exp10). if (num_digits < 0) { // Generate the shortest representation. if (!upper) upper = &lower; bool even = (value.f & 1) == 0; num_digits = 0; char* data = buf.data(); for (;;) { int digit = numerator.divmod_assign(denominator); bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. // numerator + upper >[=] pow10: bool high = add_compare(numerator, *upper, denominator) + even > 0; data[num_digits++] = static_cast('0' + digit); if (low || high) { if (!low) { ++data[num_digits - 1]; } else if (high) { int result = add_compare(numerator, numerator, denominator); // Round half to even. if (result > 0 || (result == 0 && (digit % 2) != 0)) ++data[num_digits - 1]; } buf.try_resize(to_unsigned(num_digits)); exp10 -= num_digits - 1; return; } numerator *= 10; lower *= 10; if (upper != &lower) *upper *= 10; } } // Generate the given number of digits. exp10 -= num_digits - 1; if (num_digits == 0) { buf.try_resize(1); denominator *= 10; buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; return; } buf.try_resize(to_unsigned(num_digits)); for (int i = 0; i < num_digits - 1; ++i) { int digit = numerator.divmod_assign(denominator); buf[i] = static_cast('0' + digit); numerator *= 10; } int digit = numerator.divmod_assign(denominator); auto result = add_compare(numerator, numerator, denominator); if (result > 0 || (result == 0 && (digit % 2) != 0)) { if (digit == 9) { const auto overflow = '0' + 10; buf[num_digits - 1] = overflow; // Propagate the carry. for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { buf[i] = '0'; ++buf[i - 1]; } if (buf[0] == overflow) { buf[0] = '1'; ++exp10; } return; } ++digit; } buf[num_digits - 1] = static_cast('0' + digit); } template int format_float(T value, int precision, float_specs specs, buffer& buf) { static_assert(!std::is_same::value, ""); FMT_ASSERT(value >= 0, "value is negative"); const bool fixed = specs.format == float_format::fixed; if (value <= 0) { // <= instead of == to silence a warning. if (precision <= 0 || !fixed) { buf.push_back('0'); return 0; } buf.try_resize(to_unsigned(precision)); std::uninitialized_fill_n(buf.data(), precision, '0'); return -precision; } if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf); if (precision < 0) { // Use Dragonbox for the shortest format. if (specs.binary32) { auto dec = dragonbox::to_decimal(static_cast(value)); write(buffer_appender(buf), dec.significand); return dec.exponent; } auto dec = dragonbox::to_decimal(static_cast(value)); write(buffer_appender(buf), dec.significand); return dec.exponent; } // Use Grisu + Dragon4 for the given precision: // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. int exp = 0; const int min_exp = -60; // alpha in Grisu. int cached_exp10 = 0; // K in Grisu. fp normalized = normalize(fp(value)); const auto cached_pow = get_cached_power( min_exp - (normalized.e + fp::significand_size), cached_exp10); normalized = normalized * cached_pow; // Limit precision to the maximum possible number of significant digits in an // IEEE754 double because we don't need to generate zeros. const int max_double_digits = 767; if (precision > max_double_digits) precision = max_double_digits; fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) { exp += handler.size - cached_exp10 - 1; fallback_format(value, handler.precision, specs.binary32, buf, exp); } else { exp += handler.exp10; buf.try_resize(to_unsigned(handler.size)); } if (!fixed && !specs.showpoint) { // Remove trailing zeros. auto num_digits = buf.size(); while (num_digits > 0 && buf[num_digits - 1] == '0') { --num_digits; ++exp; } buf.try_resize(num_digits); } return exp; } // namespace detail template int snprintf_float(T value, int precision, float_specs specs, buffer& buf) { // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); static_assert(!std::is_same::value, ""); // Subtract 1 to account for the difference in precision since we use %e for // both general and exponent format. if (specs.format == float_format::general || specs.format == float_format::exp) precision = (precision >= 0 ? precision : 6) - 1; // Build the format string. enum { max_format_size = 7 }; // The longest format is "%#.*Le". char format[max_format_size]; char* format_ptr = format; *format_ptr++ = '%'; if (specs.showpoint && specs.format == float_format::hex) *format_ptr++ = '#'; if (precision >= 0) { *format_ptr++ = '.'; *format_ptr++ = '*'; } if (std::is_same()) *format_ptr++ = 'L'; *format_ptr++ = specs.format != float_format::hex ? (specs.format == float_format::fixed ? 'f' : 'e') : (specs.upper ? 'A' : 'a'); *format_ptr = '\0'; // Format using snprintf. auto offset = buf.size(); for (;;) { auto begin = buf.data() + offset; auto capacity = buf.capacity() - offset; #ifdef FMT_FUZZ if (precision > 100000) throw std::runtime_error( "fuzz mode - avoid large allocation inside snprintf"); #endif // Suppress the warning about a nonliteral format string. // Cannot use auto because of a bug in MinGW (#1532). int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; int result = precision >= 0 ? snprintf_ptr(begin, capacity, format, precision, value) : snprintf_ptr(begin, capacity, format, value); if (result < 0) { // The buffer will grow exponentially. buf.try_reserve(buf.capacity() + 1); continue; } auto size = to_unsigned(result); // Size equal to capacity means that the last character was truncated. if (size >= capacity) { buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. continue; } auto is_digit = [](char c) { return c >= '0' && c <= '9'; }; if (specs.format == float_format::fixed) { if (precision == 0) { buf.try_resize(size); return 0; } // Find and remove the decimal point. auto end = begin + size, p = end; do { --p; } while (is_digit(*p)); int fraction_size = static_cast(end - p - 1); std::memmove(p, p + 1, to_unsigned(fraction_size)); buf.try_resize(size - 1); return -fraction_size; } if (specs.format == float_format::hex) { buf.try_resize(size + offset); return 0; } // Find and parse the exponent. auto end = begin + size, exp_pos = end; do { --exp_pos; } while (*exp_pos != 'e'); char sign = exp_pos[1]; FMT_ASSERT(sign == '+' || sign == '-', ""); int exp = 0; auto p = exp_pos + 2; // Skip 'e' and sign. do { FMT_ASSERT(is_digit(*p), ""); exp = exp * 10 + (*p++ - '0'); } while (p != end); if (sign == '-') exp = -exp; int fraction_size = 0; if (exp_pos != begin + 1) { // Remove trailing zeros. auto fraction_end = exp_pos - 1; while (*fraction_end == '0') --fraction_end; // Move the fractional part left to get rid of the decimal point. fraction_size = static_cast(fraction_end - begin - 1); std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size)); } buf.try_resize(to_unsigned(fraction_size) + offset + 1); return exp - fraction_size; } } } // namespace detail template <> struct formatter { FMT_CONSTEXPR format_parse_context::iterator parse( format_parse_context& ctx) { return ctx.begin(); } format_context::iterator format(const detail::bigint& n, format_context& ctx) { auto out = ctx.out(); bool first = true; for (auto i = n.bigits_.size(); i > 0; --i) { auto value = n.bigits_[i - 1u]; if (first) { out = format_to(out, FMT_STRING("{:x}"), value); first = false; continue; } out = format_to(out, FMT_STRING("{:08x}"), value); } if (n.exp_ > 0) out = format_to(out, FMT_STRING("p{}"), n.exp_ * detail::bigint::bigit_bits); return out; } }; FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { for_each_codepoint(s, [this](uint32_t cp, int error) { if (error != 0) FMT_THROW(std::runtime_error("invalid utf8")); if (cp <= 0xFFFF) { buffer_.push_back(static_cast(cp)); } else { cp -= 0x10000; buffer_.push_back(static_cast(0xD800 + (cp >> 10))); buffer_.push_back(static_cast(0xDC00 + (cp & 0x3FF))); } }); buffer_.push_back(0); } FMT_FUNC void format_system_error(detail::buffer& out, int error_code, const char* message) FMT_NOEXCEPT { FMT_TRY { auto ec = std::error_code(error_code, std::generic_category()); write(std::back_inserter(out), std::system_error(ec, message).what()); return; } FMT_CATCH(...) {} format_error_code(out, error_code, message); } FMT_FUNC void detail::error_handler::on_error(const char* message) { FMT_THROW(format_error(message)); } FMT_FUNC void report_system_error(int error_code, const char* message) FMT_NOEXCEPT { report_error(format_system_error, error_code, message); } FMT_FUNC std::string vformat(string_view fmt, format_args args) { // Don't optimize the "{}" case to keep the binary size small and because it // can be better optimized in fmt::format anyway. auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); return to_string(buffer); } #ifdef _WIN32 namespace detail { using dword = conditional_t; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // void*, const void*, dword, dword*, void*); } // namespace detail #endif namespace detail { FMT_FUNC void print(std::FILE* f, string_view text) { #ifdef _WIN32 auto fd = _fileno(f); if (_isatty(fd)) { detail::utf8_to_utf16 u16(string_view(text.data(), text.size())); auto written = detail::dword(); if (detail::WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), static_cast(u16.size()), &written, nullptr)) { return; } // Fallback to fwrite on failure. It can happen if the output has been // redirected to NUL. } #endif detail::fwrite_fully(text.data(), 1, text.size(), f); } } // namespace detail FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { memory_buffer buffer; detail::vformat_to(buffer, format_str, args); detail::print(f, {buffer.data(), buffer.size()}); } #ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, format_args args) { memory_buffer buffer; detail::vformat_to(buffer, format_str, basic_format_args>(args)); fwrite_fully(buffer.data(), 1, buffer.size(), f); } #endif FMT_FUNC void vprint(string_view format_str, format_args args) { vprint(stdout, format_str, args); } FMT_END_NAMESPACE #endif // FMT_FORMAT_INL_H_ fastnetmon-1.2.8/src/fmt/format.h000066400000000000000000003122131472727706000167340ustar00rootroot00000000000000/* Formatting library for C++ Copyright (c) 2012 - present, Victor Zverovich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- Optional exception to the license --- As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into a machine-executable object form of such source code, you may redistribute such embedded portions in such object form without including the above copyright and permission notices. */ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ #include // std::signbit #include // uint32_t #include // std::numeric_limits #include // std::uninitialized_copy #include // std::runtime_error #include // std::system_error #include // std::swap #include "core.h" #ifdef __INTEL_COMPILER # define FMT_ICC_VERSION __INTEL_COMPILER #elif defined(__ICL) # define FMT_ICC_VERSION __ICL #else # define FMT_ICC_VERSION 0 #endif #ifdef __NVCC__ # define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) #else # define FMT_CUDA_VERSION 0 #endif #ifdef __has_builtin # define FMT_HAS_BUILTIN(x) __has_builtin(x) #else # define FMT_HAS_BUILTIN(x) 0 #endif #if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_NOINLINE __attribute__((noinline)) #else # define FMT_NOINLINE #endif #if FMT_MSC_VER # define FMT_MSC_DEFAULT = default #else # define FMT_MSC_DEFAULT #endif #ifndef FMT_THROW # if FMT_EXCEPTIONS # if FMT_MSC_VER || FMT_NVCC FMT_BEGIN_NAMESPACE namespace detail { template inline void do_throw(const Exception& x) { // Silence unreachable code warnings in MSVC and NVCC because these // are nearly impossible to fix in a generic code. volatile bool b = true; if (b) throw x; } } // namespace detail FMT_END_NAMESPACE # define FMT_THROW(x) detail::do_throw(x) # else # define FMT_THROW(x) throw x # endif # else # define FMT_THROW(x) \ do { \ FMT_ASSERT(false, (x).what()); \ } while (false) # endif #endif #if FMT_EXCEPTIONS # define FMT_TRY try # define FMT_CATCH(x) catch (x) #else # define FMT_TRY if (true) # define FMT_CATCH(x) if (false) #endif #ifndef FMT_DEPRECATED # if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 # define FMT_DEPRECATED [[deprecated]] # else # if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) # define FMT_DEPRECATED __attribute__((deprecated)) # elif FMT_MSC_VER # define FMT_DEPRECATED __declspec(deprecated) # else # define FMT_DEPRECATED /* deprecated */ # endif # endif #endif // Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. #if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC # define FMT_DEPRECATED_ALIAS #else # define FMT_DEPRECATED_ALIAS FMT_DEPRECATED #endif #ifndef FMT_USE_USER_DEFINED_LITERALS // EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. # if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ FMT_MSC_VER >= 1900) && \ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) # define FMT_USE_USER_DEFINED_LITERALS 1 # else # define FMT_USE_USER_DEFINED_LITERALS 0 # endif #endif // Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of // integer formatter template instantiations to just one by only using the // largest integer type. This results in a reduction in binary size but will // cause a decrease in integer formatting performance. #if !defined(FMT_REDUCE_INT_INSTANTIATIONS) # define FMT_REDUCE_INT_INSTANTIATIONS 0 #endif // __builtin_clz is broken in clang with Microsoft CodeGen: // https://github.com/fmtlib/fmt/issues/519 #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER # define FMT_BUILTIN_CLZ(n) __builtin_clz(n) #endif #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll)) && !FMT_MSC_VER # define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) #endif #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz)) # define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) #endif #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctzll)) # define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) #endif #if FMT_MSC_VER # include // _BitScanReverse[64], _BitScanForward[64], _umul128 #endif // Some compilers masquerade as both MSVC and GCC-likes or otherwise support // __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the // MSVC intrinsics if the clz and clzll builtins are not available. #if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(FMT_BUILTIN_CTZLL) FMT_BEGIN_NAMESPACE namespace detail { // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. # if !defined(__clang__) # pragma managed(push, off) # pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) # if defined(_WIN64) # pragma intrinsic(_BitScanForward64) # pragma intrinsic(_BitScanReverse64) # endif # endif inline auto clz(uint32_t x) -> int { unsigned long r = 0; _BitScanReverse(&r, x); FMT_ASSERT(x != 0, ""); // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. FMT_MSC_WARNING(suppress : 6102) return 31 ^ static_cast(r); } # define FMT_BUILTIN_CLZ(n) detail::clz(n) inline auto clzll(uint64_t x) -> int { unsigned long r = 0; # ifdef _WIN64 _BitScanReverse64(&r, x); # else // Scan the high 32 bits. if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ (r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. return 63 ^ static_cast(r); } # define FMT_BUILTIN_CLZLL(n) detail::clzll(n) inline auto ctz(uint32_t x) -> int { unsigned long r = 0; _BitScanForward(&r, x); FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. return static_cast(r); } # define FMT_BUILTIN_CTZ(n) detail::ctz(n) inline auto ctzll(uint64_t x) -> int { unsigned long r = 0; FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. # ifdef _WIN64 _BitScanForward64(&r, x); # else // Scan the low 32 bits. if (_BitScanForward(&r, static_cast(x))) return static_cast(r); // Scan the high 32 bits. _BitScanForward(&r, static_cast(x >> 32)); r += 32; # endif return static_cast(r); } # define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) # if !defined(__clang__) # pragma managed(pop) # endif } // namespace detail FMT_END_NAMESPACE #endif FMT_BEGIN_NAMESPACE namespace detail { #if __cplusplus >= 202002L || \ (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002) # define FMT_CONSTEXPR20 constexpr #else # define FMT_CONSTEXPR20 #endif // An equivalent of `*reinterpret_cast(&source)` that doesn't have // undefined behavior (e.g. due to type aliasing). // Example: uint64_t d = bit_cast(2.718); template inline auto bit_cast(const Source& source) -> Dest { static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); Dest dest; std::memcpy(&dest, &source, sizeof(dest)); return dest; } inline auto is_big_endian() -> bool { const auto u = 1u; struct bytes { char data[sizeof(u)]; }; return bit_cast(u).data[0] == 0; } // A fallback implementation of uintptr_t for systems that lack it. struct fallback_uintptr { unsigned char value[sizeof(void*)]; fallback_uintptr() = default; explicit fallback_uintptr(const void* p) { *this = bit_cast(p); if (is_big_endian()) { for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j) std::swap(value[i], value[j]); } } }; #ifdef UINTPTR_MAX using uintptr_t = ::uintptr_t; inline auto to_uintptr(const void* p) -> uintptr_t { return bit_cast(p); } #else using uintptr_t = fallback_uintptr; inline auto to_uintptr(const void* p) -> fallback_uintptr { return fallback_uintptr(p); } #endif // Returns the largest possible value for type T. Same as // std::numeric_limits::max() but shorter and not affected by the max macro. template constexpr auto max_value() -> T { return (std::numeric_limits::max)(); } template constexpr auto num_bits() -> int { return std::numeric_limits::digits; } // std::numeric_limits::digits may return 0 for 128-bit ints. template <> constexpr auto num_bits() -> int { return 128; } template <> constexpr auto num_bits() -> int { return 128; } template <> constexpr auto num_bits() -> int { return static_cast(sizeof(void*) * std::numeric_limits::digits); } FMT_INLINE void assume(bool condition) { (void)condition; #if FMT_HAS_BUILTIN(__builtin_assume) __builtin_assume(condition); #endif } // An approximation of iterator_t for pre-C++20 systems. template using iterator_t = decltype(std::begin(std::declval())); template using sentinel_t = decltype(std::end(std::declval())); // A workaround for std::string not having mutable data() until C++17. template inline auto get_data(std::basic_string& s) -> Char* { return &s[0]; } template inline auto get_data(Container& c) -> typename Container::value_type* { return c.data(); } #if defined(_SECURE_SCL) && _SECURE_SCL // Make a checked iterator to avoid MSVC warnings. template using checked_ptr = stdext::checked_array_iterator; template auto make_checked(T* p, size_t size) -> checked_ptr { return {p, size}; } #else template using checked_ptr = T*; template inline auto make_checked(T* p, size_t) -> T* { return p; } #endif // Attempts to reserve space for n extra characters in the output range. // Returns a pointer to the reserved range or a reference to it. template ::value)> #if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) #endif inline auto reserve(std::back_insert_iterator it, size_t n) -> checked_ptr { Container& c = get_container(it); size_t size = c.size(); c.resize(size + n); return make_checked(get_data(c) + size, n); } template inline auto reserve(buffer_appender it, size_t n) -> buffer_appender { buffer& buf = get_container(it); buf.try_reserve(buf.size() + n); return it; } template constexpr auto reserve(Iterator& it, size_t) -> Iterator& { return it; } template using reserve_iterator = remove_reference_t(), 0))>; template constexpr auto to_pointer(OutputIt, size_t) -> T* { return nullptr; } template auto to_pointer(buffer_appender it, size_t n) -> T* { buffer& buf = get_container(it); auto size = buf.size(); if (buf.capacity() < size + n) return nullptr; buf.try_resize(size + n); return buf.data() + size; } template ::value)> inline auto base_iterator(std::back_insert_iterator& it, checked_ptr) -> std::back_insert_iterator { return it; } template constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { return it; } // is spectacularly slow to compile in C++20 so use a simple fill_n // instead (#1998). template FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) -> OutputIt { for (Size i = 0; i < count; ++i) *out++ = value; return out; } template FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { if (is_constant_evaluated()) { return fill_n(out, count, value); } std::memset(out, value, to_unsigned(count)); return out + count; } #ifdef __cpp_char8_t using char8_type = char8_t; #else enum char8_type : unsigned char {}; #endif template FMT_CONSTEXPR FMT_NOINLINE auto copy_str_noinline(InputIt begin, InputIt end, OutputIt out) -> OutputIt { return copy_str(begin, end, out); } // A public domain branchless UTF-8 decoder by Christopher Wellons: // https://github.com/skeeto/branchless-utf8 /* Decode the next character, c, from s, reporting errors in e. * * Since this is a branchless decoder, four bytes will be read from the * buffer regardless of the actual length of the next character. This * means the buffer _must_ have at least three bytes of zero padding * following the end of the data stream. * * Errors are reported in e, which will be non-zero if the parsed * character was somehow invalid: invalid byte sequence, non-canonical * encoding, or a surrogate half. * * The function returns a pointer to the next character. When an error * occurs, this pointer will be a guess that depends on the particular * error, but it will always advance at least one byte. */ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) -> const char* { constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; constexpr const int shiftc[] = {0, 18, 12, 6, 0}; constexpr const int shifte[] = {0, 6, 4, 2, 0}; int len = code_point_length(s); const char* next = s + len; // Assume a four-byte character and load four bytes. Unused bits are // shifted out. *c = uint32_t(s[0] & masks[len]) << 18; *c |= uint32_t(s[1] & 0x3f) << 12; *c |= uint32_t(s[2] & 0x3f) << 6; *c |= uint32_t(s[3] & 0x3f) << 0; *c >>= shiftc[len]; // Accumulate the various error conditions. using uchar = unsigned char; *e = (*c < mins[len]) << 6; // non-canonical encoding *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? *e |= (*c > 0x10FFFF) << 8; // out of range? *e |= (uchar(s[1]) & 0xc0) >> 2; *e |= (uchar(s[2]) & 0xc0) >> 4; *e |= uchar(s[3]) >> 6; *e ^= 0x2a; // top two bits of each tail byte correct? *e >>= shifte[len]; return next; } template FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { auto decode = [f](const char* p) { auto cp = uint32_t(); auto error = 0; p = utf8_decode(p, &cp, &error); f(cp, error); return p; }; auto p = s.data(); const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. if (s.size() >= block_size) { for (auto end = p + s.size() - block_size + 1; p < end;) p = decode(p); } if (auto num_chars_left = s.data() + s.size() - p) { char buf[2 * block_size - 1] = {}; copy_str(p, p + num_chars_left, buf); p = buf; do { p = decode(p); } while (p - buf < num_chars_left); } } template inline auto compute_width(basic_string_view s) -> size_t { return s.size(); } // Computes approximate display width of a UTF-8 string. FMT_CONSTEXPR inline size_t compute_width(string_view s) { size_t num_code_points = 0; // It is not a lambda for compatibility with C++14. struct count_code_points { size_t* count; FMT_CONSTEXPR void operator()(uint32_t cp, int error) const { *count += detail::to_unsigned( 1 + (error == 0 && cp >= 0x1100 && (cp <= 0x115f || // Hangul Jamo init. consonants cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET〈 cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET 〉 // CJK ... Yi except Unicode Character “〿”: (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms (cp >= 0x20000 && cp <= 0x2fffd) || // CJK (cp >= 0x30000 && cp <= 0x3fffd) || // Miscellaneous Symbols and Pictographs + Emoticons: (cp >= 0x1f300 && cp <= 0x1f64f) || // Supplemental Symbols and Pictographs: (cp >= 0x1f900 && cp <= 0x1f9ff)))); } }; for_each_codepoint(s, count_code_points{&num_code_points}); return num_code_points; } inline auto compute_width(basic_string_view s) -> size_t { return compute_width(basic_string_view( reinterpret_cast(s.data()), s.size())); } template inline auto code_point_index(basic_string_view s, size_t n) -> size_t { size_t size = s.size(); return n < size ? n : size; } // Calculates the index of the nth code point in a UTF-8 string. inline auto code_point_index(basic_string_view s, size_t n) -> size_t { const char8_type* data = s.data(); size_t num_code_points = 0; for (size_t i = 0, size = s.size(); i != size; ++i) { if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i; } return s.size(); } template using is_fast_float = bool_constant::is_iec559 && sizeof(T) <= sizeof(double)>; #ifndef FMT_USE_FULL_CACHE_DRAGONBOX # define FMT_USE_FULL_CACHE_DRAGONBOX 0 #endif template template void buffer::append(const U* begin, const U* end) { while (begin != end) { auto count = to_unsigned(end - begin); try_reserve(size_ + count); auto free_cap = capacity_ - size_; if (free_cap < count) count = free_cap; std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count)); size_ += count; begin += count; } } template struct is_locale : std::false_type {}; template struct is_locale> : std::true_type {}; } // namespace detail FMT_MODULE_EXPORT_BEGIN // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. enum { inline_buffer_size = 500 }; /** \rst A dynamically growing memory buffer for trivially copyable/constructible types with the first ``SIZE`` elements stored in the object itself. You can use the ```memory_buffer`` type alias for ``char`` instead. **Example**:: fmt::memory_buffer out; format_to(out, "The answer is {}.", 42); This will append the following output to the ``out`` object: .. code-block:: none The answer is 42. The output can be converted to an ``std::string`` with ``to_string(out)``. \endrst */ template > class basic_memory_buffer final : public detail::buffer { private: T store_[SIZE]; // Don't inherit from Allocator avoid generating type_info for it. Allocator alloc_; // Deallocate memory allocated by the buffer. void deallocate() { T* data = this->data(); if (data != store_) alloc_.deallocate(data, this->capacity()); } protected: void grow(size_t size) final FMT_OVERRIDE; public: using value_type = T; using const_reference = const T&; explicit basic_memory_buffer(const Allocator& alloc = Allocator()) : alloc_(alloc) { this->set(store_, SIZE); } ~basic_memory_buffer() { deallocate(); } private: // Move data from other to this buffer. void move(basic_memory_buffer& other) { alloc_ = std::move(other.alloc_); T* data = other.data(); size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); std::uninitialized_copy(other.store_, other.store_ + size, detail::make_checked(store_, capacity)); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called // when deallocating. other.set(other.store_, 0); } this->resize(size); } public: /** \rst Constructs a :class:`fmt::basic_memory_buffer` object moving the content of the other object to it. \endrst */ basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); } /** \rst Moves the content of the other ``basic_memory_buffer`` object to this one. \endrst */ auto operator=(basic_memory_buffer&& other) FMT_NOEXCEPT -> basic_memory_buffer& { FMT_ASSERT(this != &other, ""); deallocate(); move(other); return *this; } // Returns a copy of the allocator associated with this buffer. auto get_allocator() const -> Allocator { return alloc_; } /** Resizes the buffer to contain *count* elements. If T is a POD type new elements may not be initialized. */ void resize(size_t count) { this->try_resize(count); } /** Increases the buffer capacity to *new_capacity*. */ void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } // Directly append data into the buffer using detail::buffer::append; template void append(const ContiguousRange& range) { append(range.data(), range.data() + range.size()); } }; template void basic_memory_buffer::grow(size_t size) { #ifdef FMT_FUZZ if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much"); #endif const size_t max_size = std::allocator_traits::max_size(alloc_); size_t old_capacity = this->capacity(); size_t new_capacity = old_capacity + old_capacity / 2; if (size > new_capacity) new_capacity = size; else if (new_capacity > max_size) new_capacity = size > max_size ? size : max_size; T* old_data = this->data(); T* new_data = std::allocator_traits::allocate(alloc_, new_capacity); // The following code doesn't throw, so the raw pointer above doesn't leak. std::uninitialized_copy(old_data, old_data + this->size(), detail::make_checked(new_data, new_capacity)); this->set(new_data, new_capacity); // deallocate must not throw according to the standard, but even if it does, // the buffer already uses the new storage and will deallocate it in // destructor. if (old_data != store_) alloc_.deallocate(old_data, old_capacity); } using memory_buffer = basic_memory_buffer; template struct is_contiguous> : std::true_type { }; namespace detail { FMT_API void print(std::FILE*, string_view); } /** A formatting error such as invalid format string. */ FMT_CLASS_API class FMT_API format_error : public std::runtime_error { public: explicit format_error(const char* message) : std::runtime_error(message) {} explicit format_error(const std::string& message) : std::runtime_error(message) {} format_error(const format_error&) = default; format_error& operator=(const format_error&) = default; format_error(format_error&&) = default; format_error& operator=(format_error&&) = default; ~format_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT; }; /** \rst Constructs a `~fmt::format_arg_store` object that contains references to arguments and can be implicitly converted to `~fmt::format_args`. If ``fmt`` is a compile-time string then `make_args_checked` checks its validity at compile time. \endrst */ template > FMT_INLINE auto make_args_checked(const S& fmt, const remove_reference_t&... args) -> format_arg_store, remove_reference_t...> { static_assert( detail::count<( std::is_base_of>::value && std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); detail::check_format_string(fmt); return {args...}; } // compile-time support namespace detail_exported { #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS template struct fixed_string { constexpr fixed_string(const Char (&str)[N]) { detail::copy_str(static_cast(str), str + N, data); } Char data[N]{}; }; #endif // Converts a compile-time string to basic_string_view. template constexpr auto compile_string_to_view(const Char (&s)[N]) -> basic_string_view { // Remove trailing NUL character if needed. Won't be present if this is used // with a raw character array (i.e. not defined as a string). return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; } template constexpr auto compile_string_to_view(detail::std_string_view s) -> basic_string_view { return {s.data(), s.size()}; } } // namespace detail_exported FMT_BEGIN_DETAIL_NAMESPACE inline void throw_format_error(const char* message) { FMT_THROW(format_error(message)); } template struct is_integral : std::is_integral {}; template <> struct is_integral : std::true_type {}; template <> struct is_integral : std::true_type {}; template using is_signed = std::integral_constant::is_signed || std::is_same::value>; // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. template ::value)> FMT_CONSTEXPR auto is_negative(T value) -> bool { return value < 0; } template ::value)> FMT_CONSTEXPR auto is_negative(T) -> bool { return false; } template ::value)> FMT_CONSTEXPR auto is_supported_floating_point(T) -> uint16_t { return (std::is_same::value && FMT_USE_FLOAT) || (std::is_same::value && FMT_USE_DOUBLE) || (std::is_same::value && FMT_USE_LONG_DOUBLE); } // Smallest of uint32_t, uint64_t, uint128_t that is large enough to // represent all values of an integral type T. template using uint32_or_64_or_128_t = conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, uint32_t, conditional_t() <= 64, uint64_t, uint128_t>>; template using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; #define FMT_POWERS_OF_10(factor) \ factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ (factor)*1000000, (factor)*10000000, (factor)*100000000, \ (factor)*1000000000 // Static data is placed in this class template for the header-only config. template struct basic_data { // log10(2) = 0x0.4d104d427de7fbcc... static const uint64_t log10_2_significand = 0x4d104d427de7fbcc; // GCC generates slightly better code for pairs than chars. FMT_API static constexpr const char digits[][2] = { {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; FMT_API static constexpr const char hex_digits[] = "0123456789abcdef"; FMT_API static constexpr const char signs[] = {0, '-', '+', ' '}; FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '}; FMT_API static constexpr const char left_padding_shifts[] = {31, 31, 0, 1, 0}; FMT_API static constexpr const char right_padding_shifts[] = {0, 31, 0, 1, 0}; }; #ifdef FMT_SHARED // Required for -flto, -fivisibility=hidden and -shared to work extern template struct basic_data; #endif // This is a struct rather than an alias to avoid shadowing warnings in gcc. struct data : basic_data<> {}; template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { int count = 1; for (;;) { // Integer division is slow so do it for a group of four digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. if (n < 10) return count; if (n < 100) return count + 1; if (n < 1000) return count + 2; if (n < 10000) return count + 3; n /= 10000u; count += 4; } } #if FMT_USE_INT128 FMT_CONSTEXPR inline auto count_digits(uint128_t n) -> int { return count_digits_fallback(n); } #endif // Returns the number of decimal digits in n. Leading zeros are not counted // except for n == 0 in which case count_digits returns 1. FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { #ifdef FMT_BUILTIN_CLZLL if (!is_constant_evaluated()) { // https://github.com/fmtlib/format-benchmark/blob/master/digits10 // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). constexpr uint16_t bsr2log10[] = { 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; constexpr const uint64_t zero_or_powers_of_10[] = { 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), 10000000000000000000ULL}; return t - (n < zero_or_powers_of_10[t]); } #endif return count_digits_fallback(n); } // Counts the number of digits in n. BITS = log2(radix). template FMT_CONSTEXPR auto count_digits(UInt n) -> int { #ifdef FMT_BUILTIN_CLZ if (num_bits() == 32) return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; #endif int num_digits = 0; do { ++num_digits; } while ((n >>= BITS) != 0); return num_digits; } template <> auto count_digits<4>(detail::fallback_uintptr n) -> int; // It is a separate function rather than a part of count_digits to workaround // the lack of static constexpr in constexpr functions. FMT_INLINE uint64_t count_digits_inc(int n) { // An optimization by Kendall Willets from https://bit.ly/3uOIQrB. // This increments the upper 32 bits (log10(T) - 1) when >= T is added. #define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) static constexpr uint64_t table[] = { FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M FMT_INC(1000000000), FMT_INC(1000000000) // 4B }; return table[n]; } // Optional version of count_digits for better performance on 32-bit platforms. FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { #ifdef FMT_BUILTIN_CLZ if (!is_constant_evaluated()) { auto inc = count_digits_inc(FMT_BUILTIN_CLZ(n | 1) ^ 31); return static_cast((n + inc) >> 32); } #endif return count_digits_fallback(n); } template constexpr auto digits10() FMT_NOEXCEPT -> int { return std::numeric_limits::digits10; } template <> constexpr auto digits10() FMT_NOEXCEPT -> int { return 38; } template <> constexpr auto digits10() FMT_NOEXCEPT -> int { return 38; } template struct thousands_sep_result { std::string grouping; Char thousands_sep; }; template FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; template inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { auto result = thousands_sep_impl(loc); return {result.grouping, Char(result.thousands_sep)}; } template <> inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { return thousands_sep_impl(loc); } template FMT_API auto decimal_point_impl(locale_ref loc) -> Char; template inline auto decimal_point(locale_ref loc) -> Char { return Char(decimal_point_impl(loc)); } template <> inline auto decimal_point(locale_ref loc) -> wchar_t { return decimal_point_impl(loc); } // Compares two characters for equality. template auto equal2(const Char* lhs, const char* rhs) -> bool { return lhs[0] == rhs[0] && lhs[1] == rhs[1]; } inline auto equal2(const char* lhs, const char* rhs) -> bool { return memcmp(lhs, rhs, 2) == 0; } // Copies two characters from src to dst. template void copy2(Char* dst, const char* src) { *dst++ = static_cast(*src++); *dst = static_cast(*src); } FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } template struct format_decimal_result { Iterator begin; Iterator end; }; // Formats a decimal unsigned integer value writing into out pointing to a // buffer of specified size. The caller must ensure that the buffer is large // enough. template FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) -> format_decimal_result { FMT_ASSERT(size >= count_digits(value), "invalid digit count"); out += size; Char* end = out; if (is_constant_evaluated()) { while (value >= 10) { *--out = static_cast('0' + value % 10); value /= 10; } *--out = static_cast('0' + value); return {out, end}; } while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. out -= 2; copy2(out, data::digits[value % 100]); value /= 100; } if (value < 10) { *--out = static_cast('0' + value); return {out, end}; } out -= 2; copy2(out, data::digits[value]); return {out, end}; } template >::value)> inline auto format_decimal(Iterator out, UInt value, int size) -> format_decimal_result { // Buffer is large enough to hold all digits (digits10 + 1). Char buffer[digits10() + 1]; auto end = format_decimal(buffer, value, size).end; return {out, detail::copy_str_noinline(buffer, end, out)}; } template FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, bool upper = false) -> Char* { buffer += num_digits; Char* end = buffer; do { const char* digits = upper ? "0123456789ABCDEF" : data::hex_digits; unsigned digit = (value & ((1 << BASE_BITS) - 1)); *--buffer = static_cast(BASE_BITS < 4 ? static_cast('0' + digit) : digits[digit]); } while ((value >>= BASE_BITS) != 0); return end; } template auto format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, bool = false) -> Char* { auto char_digits = std::numeric_limits::digits / 4; int start = (num_digits + char_digits - 1) / char_digits - 1; if (int start_digits = num_digits % char_digits) { unsigned value = n.value[start--]; buffer = format_uint(buffer, value, start_digits); } for (; start >= 0; --start) { unsigned value = n.value[start]; buffer += char_digits; auto p = buffer; for (int i = 0; i < char_digits; ++i) { unsigned digit = (value & ((1 << BASE_BITS) - 1)); *--p = static_cast(data::hex_digits[digit]); value >>= BASE_BITS; } } return buffer; } template inline auto format_uint(It out, UInt value, int num_digits, bool upper = false) -> It { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { format_uint(ptr, value, num_digits, upper); return out; } // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). char buffer[num_bits() / BASE_BITS + 1]; format_uint(buffer, value, num_digits, upper); return detail::copy_str_noinline(buffer, buffer + num_digits, out); } // A converter from UTF-8 to UTF-16. class utf8_to_utf16 { private: basic_memory_buffer buffer_; public: FMT_API explicit utf8_to_utf16(string_view s); operator basic_string_view() const { return {&buffer_[0], size()}; } auto size() const -> size_t { return buffer_.size() - 1; } auto c_str() const -> const wchar_t* { return &buffer_[0]; } auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; namespace dragonbox { // Type-specific information that Dragonbox uses. template struct float_info; template <> struct float_info { using carrier_uint = uint32_t; static const int significand_bits = 23; static const int exponent_bits = 8; static const int min_exponent = -126; static const int max_exponent = 127; static const int exponent_bias = -127; static const int decimal_digits = 9; static const int kappa = 1; static const int big_divisor = 100; static const int small_divisor = 10; static const int min_k = -31; static const int max_k = 46; static const int cache_bits = 64; static const int divisibility_check_by_5_threshold = 39; static const int case_fc_pm_half_lower_threshold = -1; static const int case_fc_pm_half_upper_threshold = 6; static const int case_fc_lower_threshold = -2; static const int case_fc_upper_threshold = 6; static const int case_shorter_interval_left_endpoint_lower_threshold = 2; static const int case_shorter_interval_left_endpoint_upper_threshold = 3; static const int shorter_interval_tie_lower_threshold = -35; static const int shorter_interval_tie_upper_threshold = -35; static const int max_trailing_zeros = 7; }; template <> struct float_info { using carrier_uint = uint64_t; static const int significand_bits = 52; static const int exponent_bits = 11; static const int min_exponent = -1022; static const int max_exponent = 1023; static const int exponent_bias = -1023; static const int decimal_digits = 17; static const int kappa = 2; static const int big_divisor = 1000; static const int small_divisor = 100; static const int min_k = -292; static const int max_k = 326; static const int cache_bits = 128; static const int divisibility_check_by_5_threshold = 86; static const int case_fc_pm_half_lower_threshold = -2; static const int case_fc_pm_half_upper_threshold = 9; static const int case_fc_lower_threshold = -4; static const int case_fc_upper_threshold = 9; static const int case_shorter_interval_left_endpoint_lower_threshold = 2; static const int case_shorter_interval_left_endpoint_upper_threshold = 3; static const int shorter_interval_tie_lower_threshold = -77; static const int shorter_interval_tie_upper_threshold = -77; static const int max_trailing_zeros = 16; }; template struct decimal_fp { using significand_type = typename float_info::carrier_uint; significand_type significand; int exponent; }; template FMT_API auto to_decimal(T x) FMT_NOEXCEPT -> decimal_fp; } // namespace dragonbox template constexpr auto exponent_mask() -> typename dragonbox::float_info::carrier_uint { using uint = typename dragonbox::float_info::carrier_uint; return ((uint(1) << dragonbox::float_info::exponent_bits) - 1) << dragonbox::float_info::significand_bits; } // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. template auto write_exponent(int exp, It it) -> It { FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); if (exp < 0) { *it++ = static_cast('-'); exp = -exp; } else { *it++ = static_cast('+'); } if (exp >= 100) { const char* top = data::digits[exp / 100]; if (exp >= 1000) *it++ = static_cast(top[0]); *it++ = static_cast(top[1]); exp %= 100; } const char* d = data::digits[exp]; *it++ = static_cast(d[0]); *it++ = static_cast(d[1]); return it; } template auto format_float(T value, int precision, float_specs specs, buffer& buf) -> int; // Formats a floating-point number with snprintf. template auto snprintf_float(T value, int precision, float_specs specs, buffer& buf) -> int; template auto promote_float(T value) -> T { return value; } inline auto promote_float(float value) -> double { return static_cast(value); } template FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, const fill_t& fill) -> OutputIt { auto fill_size = fill.size(); if (fill_size == 1) return detail::fill_n(it, n, fill[0]); auto data = fill.data(); for (size_t i = 0; i < n; ++i) it = copy_str(data, data + fill_size, it); return it; } // Writes the output of f, padded according to format specifications in specs. // size: output size in code units. // width: output display width in (terminal) column positions. template FMT_CONSTEXPR auto write_padded(OutputIt out, const basic_format_specs& specs, size_t size, size_t width, F&& f) -> OutputIt { static_assert(align == align::left || align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); size_t padding = spec_width > width ? spec_width - width : 0; auto* shifts = align == align::left ? data::left_padding_shifts : data::right_padding_shifts; size_t left_padding = padding >> shifts[specs.align]; size_t right_padding = padding - left_padding; auto it = reserve(out, size + padding * specs.fill.size()); if (left_padding != 0) it = fill(it, left_padding, specs.fill); it = f(it); if (right_padding != 0) it = fill(it, right_padding, specs.fill); return base_iterator(out, it); } template constexpr auto write_padded(OutputIt out, const basic_format_specs& specs, size_t size, F&& f) -> OutputIt { return write_padded(out, specs, size, size, f); } template FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, const basic_format_specs& specs) -> OutputIt { return write_padded( out, specs, bytes.size(), [bytes](reserve_iterator it) { const char* data = bytes.data(); return copy_str(data, data + bytes.size(), it); }); } template auto write_ptr(OutputIt out, UIntPtr value, const basic_format_specs* specs) -> OutputIt { int num_digits = count_digits<4>(value); auto size = to_unsigned(num_digits) + size_t(2); auto write = [=](reserve_iterator it) { *it++ = static_cast('0'); *it++ = static_cast('x'); return format_uint<4, Char>(it, value, num_digits); }; return specs ? write_padded(out, *specs, size, write) : base_iterator(out, write(reserve(out, size))); } template FMT_CONSTEXPR auto write_char(OutputIt out, Char value, const basic_format_specs& specs) -> OutputIt { return write_padded(out, specs, 1, [=](reserve_iterator it) { *it++ = value; return it; }); } template FMT_CONSTEXPR auto write(OutputIt out, Char value, const basic_format_specs& specs, locale_ref loc = {}) -> OutputIt { return check_char_specs(specs) ? write_char(out, value, specs) : write(out, static_cast(value), specs, loc); } // Data for write_int that doesn't depend on output iterator type. It is used to // avoid template code bloat. template struct write_int_data { size_t size; size_t padding; FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, const basic_format_specs& specs) : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { auto width = to_unsigned(specs.width); if (width > size) { padding = width - size; size = width; } } else if (specs.precision > num_digits) { size = (prefix >> 24) + to_unsigned(specs.precision); padding = to_unsigned(specs.precision - num_digits); } } }; // Writes an integer in the format // // where are written by write_digits(it). // prefix contains chars in three lower bytes and the size in the fourth byte. template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, unsigned prefix, const basic_format_specs& specs, W write_digits) -> OutputIt { // Slightly faster check for specs.width == 0 && specs.precision == -1. if ((specs.width | (specs.precision + 1)) == 0) { auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); if (prefix != 0) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); } return base_iterator(out, write_digits(it)); } auto data = write_int_data(num_digits, prefix, specs); return write_padded( out, specs, data.size, [=](reserve_iterator it) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); it = detail::fill_n(it, data.padding, static_cast('0')); return write_digits(it); }); } template auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, const basic_format_specs& specs, locale_ref loc) -> bool { static_assert(std::is_same, UInt>::value, ""); const auto sep_size = 1; auto ts = thousands_sep(loc); if (!ts.thousands_sep) return false; int num_digits = count_digits(value); int size = num_digits, n = num_digits; const std::string& groups = ts.grouping; std::string::const_iterator group = groups.cbegin(); while (group != groups.cend() && n > *group && *group > 0 && *group != max_value()) { size += sep_size; n -= *group; ++group; } if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); char digits[40]; format_decimal(digits, value, num_digits); basic_memory_buffer buffer; if (prefix != 0) ++size; const auto usize = to_unsigned(size); buffer.resize(usize); basic_string_view s(&ts.thousands_sep, sep_size); // Index of a decimal digit with the least significant digit having index 0. int digit_index = 0; group = groups.cbegin(); auto p = buffer.data() + size - 1; for (int i = num_digits - 1; i > 0; --i) { *p-- = static_cast(digits[i]); if (*group <= 0 || ++digit_index % *group != 0 || *group == max_value()) continue; if (group + 1 != groups.cend()) { digit_index = 0; ++group; } std::uninitialized_copy(s.data(), s.data() + s.size(), make_checked(p, s.size())); p -= s.size(); } *p-- = static_cast(*digits); if (prefix != 0) *p = static_cast(prefix); auto data = buffer.data(); out = write_padded( out, specs, usize, usize, [=](reserve_iterator it) { return copy_str(data, data + size, it); }); return true; } FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { prefix |= prefix != 0 ? value << 8 : value; prefix += (1u + (value > 0xff ? 1 : 0)) << 24; } template struct write_int_arg { UInt abs_value; unsigned prefix; }; template FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) -> write_int_arg> { auto prefix = 0u; auto abs_value = static_cast>(value); if (is_negative(value)) { prefix = 0x01000000 | '-'; abs_value = 0 - abs_value; } else { prefix = data::prefixes[sign]; } return {abs_value, prefix}; } template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, const basic_format_specs& specs, locale_ref loc) -> OutputIt { static_assert(std::is_same>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; auto utype = static_cast(specs.type); switch (specs.type) { case 0: case 'd': { if (specs.localized && write_int_localized(out, static_cast>(abs_value), prefix, specs, loc)) { return out; } auto num_digits = count_digits(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_decimal(it, abs_value, num_digits).end; }); } case 'x': case 'X': { if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); bool upper = specs.type != 'x'; int num_digits = count_digits<4>(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_uint<4, Char>(it, abs_value, num_digits, upper); }); } case 'b': case 'B': { if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); int num_digits = count_digits<1>(abs_value); return write_int(out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_uint<1, Char>(it, abs_value, num_digits); }); } case 'o': { int num_digits = count_digits<3>(abs_value); if (specs.alt && specs.precision <= num_digits && abs_value != 0) { // Octal prefix '0' is counted as a digit, so only add it if precision // is not greater than the number of digits. prefix_append(prefix, '0'); } return write_int(out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_uint<3, Char>(it, abs_value, num_digits); }); } case 'c': return write_char(out, static_cast(abs_value), specs); default: FMT_THROW(format_error("invalid type specifier")); } return out; } template ::value && !std::is_same::value && std::is_same>::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, const basic_format_specs& specs, locale_ref loc) -> OutputIt { return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); } // An inlined version of write used in format string compilation. template ::value && !std::is_same::value && !std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, const basic_format_specs& specs, locale_ref loc) -> OutputIt { return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, const basic_format_specs& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) size = code_point_index(s, to_unsigned(specs.precision)); auto width = specs.width != 0 ? compute_width(basic_string_view(data, size)) : 0; return write_padded(out, specs, size, width, [=](reserve_iterator it) { return copy_str(data, data + size, it); }); } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view> s, const basic_format_specs& specs, locale_ref) -> OutputIt { return write(out, s, specs); } template FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const basic_format_specs& specs, locale_ref) -> OutputIt { return check_cstring_type_spec(specs.type) ? write(out, basic_string_view(s), specs, {}) : write_ptr(out, to_uintptr(s), &specs); } template auto write_nonfinite(OutputIt out, bool isinf, basic_format_specs specs, const float_specs& fspecs) -> OutputIt { auto str = isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); constexpr size_t str_size = 3; auto sign = fspecs.sign; auto size = str_size + (sign ? 1 : 0); // Replace '0'-padding with space for non-finite values. const bool is_zero_fill = specs.fill.size() == 1 && *specs.fill.data() == static_cast('0'); if (is_zero_fill) specs.fill[0] = static_cast(' '); return write_padded(out, specs, size, [=](reserve_iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); return copy_str(str, str + str_size, it); }); } // A decimal floating-point number significand * pow(10, exp). struct big_decimal_fp { const char* significand; int significand_size; int exponent; }; inline auto get_significand_size(const big_decimal_fp& fp) -> int { return fp.significand_size; } template inline auto get_significand_size(const dragonbox::decimal_fp& fp) -> int { return count_digits(fp.significand); } template inline auto write_significand(OutputIt out, const char* significand, int& significand_size) -> OutputIt { return copy_str(significand, significand + significand_size, out); } template inline auto write_significand(OutputIt out, UInt significand, int significand_size) -> OutputIt { return format_decimal(out, significand, significand_size).end; } template ::value)> inline auto write_significand(Char* out, UInt significand, int significand_size, int integral_size, Char decimal_point) -> Char* { if (!decimal_point) return format_decimal(out, significand, significand_size).end; auto end = format_decimal(out + 1, significand, significand_size).end; if (integral_size == 1) { out[0] = out[1]; } else { std::uninitialized_copy_n(out + 1, integral_size, make_checked(out, to_unsigned(integral_size))); } out[integral_size] = decimal_point; return end; } template >::value)> inline auto write_significand(OutputIt out, UInt significand, int significand_size, int integral_size, Char decimal_point) -> OutputIt { // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. Char buffer[digits10() + 2]; auto end = write_significand(buffer, significand, significand_size, integral_size, decimal_point); return detail::copy_str_noinline(buffer, end, out); } template inline auto write_significand(OutputIt out, const char* significand, int significand_size, int integral_size, Char decimal_point) -> OutputIt { out = detail::copy_str_noinline(significand, significand + integral_size, out); if (!decimal_point) return out; *out++ = decimal_point; return detail::copy_str_noinline(significand + integral_size, significand + significand_size, out); } template auto write_float(OutputIt out, const DecimalFP& fp, const basic_format_specs& specs, float_specs fspecs, Char decimal_point) -> OutputIt { auto significand = fp.significand; int significand_size = get_significand_size(fp); static const Char zero = static_cast('0'); auto sign = fspecs.sign; size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); using iterator = reserve_iterator; int output_exp = fp.exponent + significand_size - 1; auto use_exp_format = [=]() { if (fspecs.format == float_format::exp) return true; if (fspecs.format != float_format::general) return false; // Use the fixed notation if the exponent is in [exp_lower, exp_upper), // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. const int exp_lower = -4, exp_upper = 16; return output_exp < exp_lower || output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper); }; if (use_exp_format()) { int num_zeros = 0; if (fspecs.showpoint) { num_zeros = fspecs.precision - significand_size; if (num_zeros < 0) num_zeros = 0; size += to_unsigned(num_zeros); } else if (significand_size == 1) { decimal_point = Char(); } auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; int exp_digits = 2; if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); char exp_char = fspecs.upper ? 'E' : 'e'; auto write = [=](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); // Insert a decimal point after the first digit and add an exponent. it = write_significand(it, significand, significand_size, 1, decimal_point); if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); *it++ = static_cast(exp_char); return write_exponent(output_exp, it); }; return specs.width > 0 ? write_padded(out, specs, size, write) : base_iterator(out, write(reserve(out, size))); } int exp = fp.exponent + significand_size; if (fp.exponent >= 0) { // 1234e5 -> 123400000[.0+] size += to_unsigned(fp.exponent); int num_zeros = fspecs.precision - exp; #ifdef FMT_FUZZ if (num_zeros > 5000) throw std::runtime_error("fuzz mode - avoiding excessive cpu use"); #endif if (fspecs.showpoint) { if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; if (num_zeros > 0) size += to_unsigned(num_zeros) + 1; } return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); it = write_significand(it, significand, significand_size); it = detail::fill_n(it, fp.exponent, zero); if (!fspecs.showpoint) return it; *it++ = decimal_point; return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } else if (exp > 0) { // 1234e-2 -> 12.34[0+] int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); it = write_significand(it, significand, significand_size, exp, decimal_point); return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } // 1234e-6 -> 0.001234 int num_zeros = -exp; if (significand_size == 0 && fspecs.precision >= 0 && fspecs.precision < num_zeros) { num_zeros = fspecs.precision; } bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint; size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); *it++ = zero; if (!pointy) return it; *it++ = decimal_point; it = detail::fill_n(it, num_zeros, zero); return write_significand(it, significand, significand_size); }); } template ::value)> auto write(OutputIt out, T value, basic_format_specs specs, locale_ref loc = {}) -> OutputIt { if (const_check(!is_supported_floating_point(value))) return out; float_specs fspecs = parse_float_type_spec(specs); fspecs.sign = specs.sign; if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. fspecs.sign = sign::minus; value = -value; } else if (fspecs.sign == sign::minus) { fspecs.sign = sign::none; } if (!std::isfinite(value)) return write_nonfinite(out, std::isinf(value), specs, fspecs); if (specs.align == align::numeric && fspecs.sign) { auto it = reserve(out, 1); *it++ = static_cast(data::signs[fspecs.sign]); out = base_iterator(out, it); fspecs.sign = sign::none; if (specs.width != 0) --specs.width; } memory_buffer buffer; if (fspecs.format == float_format::hex) { if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); snprintf_float(promote_float(value), specs.precision, fspecs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; if (fspecs.format == float_format::exp) { if (precision == max_value()) FMT_THROW(format_error("number is too big")); else ++precision; } if (const_check(std::is_same())) fspecs.binary32 = true; fspecs.use_grisu = is_fast_float(); int exp = format_float(promote_float(value), precision, fspecs, buffer); fspecs.precision = precision; Char point = fspecs.locale ? decimal_point(loc) : static_cast('.'); auto fp = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; return write_float(out, fp, specs, fspecs, point); } template ::value)> auto write(OutputIt out, T value) -> OutputIt { if (const_check(!is_supported_floating_point(value))) return out; using floaty = conditional_t::value, double, T>; using uint = typename dragonbox::float_info::carrier_uint; auto bits = bit_cast(value); auto fspecs = float_specs(); auto sign_bit = bits & (uint(1) << (num_bits() - 1)); if (sign_bit != 0) { fspecs.sign = sign::minus; value = -value; } static const auto specs = basic_format_specs(); uint mask = exponent_mask(); if ((bits & mask) == mask) return write_nonfinite(out, std::isinf(value), specs, fspecs); auto dec = dragonbox::to_decimal(static_cast(value)); return write_float(out, dec, specs, fspecs, static_cast('.')); } template ::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { return write(out, value, basic_format_specs()); } template auto write(OutputIt out, monostate, basic_format_specs = {}, locale_ref = {}) -> OutputIt { FMT_ASSERT(false, ""); return out; } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) -> OutputIt { auto it = reserve(out, value.size()); it = copy_str_noinline(value.begin(), value.end(), it); return base_iterator(out, it); } template ::value)> constexpr auto write(OutputIt out, const T& value) -> OutputIt { return write(out, to_string_view(value)); } template ::value && !std::is_same::value && !std::is_same::value)> FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { auto abs_value = static_cast>(value); bool negative = is_negative(value); // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. if (negative) abs_value = ~abs_value + 1; int num_digits = count_digits(abs_value); auto size = (negative ? 1 : 0) + static_cast(num_digits); auto it = reserve(out, size); if (auto ptr = to_pointer(it, size)) { if (negative) *ptr++ = static_cast('-'); format_decimal(ptr, abs_value, num_digits); return out; } if (negative) *it++ = static_cast('-'); it = format_decimal(it, abs_value, num_digits).end; return base_iterator(out, it); } // FMT_ENABLE_IF() condition separated to workaround MSVC bug template < typename Char, typename OutputIt, typename T, bool check = std::is_enum::value && !std::is_same::value && mapped_type_constant>::value != type::custom_type, FMT_ENABLE_IF(check)> FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { return write( out, static_cast::type>(value)); } template ::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, const basic_format_specs& specs = {}, locale_ref = {}) -> OutputIt { return specs.type && specs.type != 's' ? write(out, value ? 1 : 0, specs, {}) : write_bytes(out, value ? "true" : "false", specs); } template FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { auto it = reserve(out, 1); *it++ = value; return base_iterator(out, it); } template FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) -> OutputIt { if (!value) { FMT_THROW(format_error("string pointer is null")); } else { auto length = std::char_traits::length(value); out = write(out, basic_string_view(value, length)); } return out; } template ::value)> auto write(OutputIt out, const T* value, const basic_format_specs& specs = {}, locale_ref = {}) -> OutputIt { check_pointer_type_spec(specs.type, error_handler()); return write_ptr(out, to_uintptr(value), &specs); } template FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> typename std::enable_if< mapped_type_constant>::value == type::custom_type, OutputIt>::type { using context_type = basic_format_context; using formatter_type = conditional_t::value, typename context_type::template formatter_type, fallback_formatter>; context_type ctx(out, {}, {}); return formatter_type().format(value, ctx); } // An argument visitor that formats the argument and writes it via the output // iterator. It's a class and not a generic lambda for compatibility with C++11. template struct default_arg_formatter { using iterator = buffer_appender; using context = buffer_context; iterator out; basic_format_args args; locale_ref loc; template auto operator()(T value) -> iterator { return write(out, value); } auto operator()(typename basic_format_arg::handle h) -> iterator { basic_format_parse_context parse_ctx({}); context format_ctx(out, args, loc); h.format(parse_ctx, format_ctx); return format_ctx.out(); } }; template struct arg_formatter { using iterator = buffer_appender; using context = buffer_context; iterator out; const basic_format_specs& specs; locale_ref locale; template FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator { return detail::write(out, value, specs, locale); } auto operator()(typename basic_format_arg::handle) -> iterator { // User-defined types are handled separately because they require access // to the parse context. return out; } }; template struct custom_formatter { basic_format_parse_context& parse_ctx; buffer_context& ctx; void operator()( typename basic_format_arg>::handle h) const { h.format(parse_ctx, ctx); } template void operator()(T) const {} }; template using is_integer = bool_constant::value && !std::is_same::value && !std::is_same::value && !std::is_same::value>; template class width_checker { public: explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { if (is_negative(value)) handler_.on_error("negative width"); return static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { handler_.on_error("width is not integer"); return 0; } private: ErrorHandler& handler_; }; template class precision_checker { public: explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { if (is_negative(value)) handler_.on_error("negative precision"); return static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { handler_.on_error("precision is not integer"); return 0; } private: ErrorHandler& handler_; }; template