pax_global_header00006660000000000000000000000064152070301000014476gustar00rootroot0000000000000052 comment=394fbe06d112f9609d3d1cb10d60cbf98347c96a pavel-odintsov-fastnetmon-394fbe0/000077500000000000000000000000001520703010000172425ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/.circleci/000077500000000000000000000000001520703010000210755ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/.circleci/config.yml000066400000000000000000000575551520703010000231060ustar00rootroot00000000000000version: 2.1 parameters: fastnetmon_build_version: type: string default: "1.2.9" 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 debhelper - 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://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://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" pavel-odintsov-fastnetmon-394fbe0/.github/000077500000000000000000000000001520703010000206025ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/.github/CONTRIBUTING.md000066400000000000000000000002321520703010000230300ustar00rootroot00000000000000Hello! Please review our [developer guides](https://github.com/pavel-odintsov/fastnetmon/blob/master/docs/DEVELOPER_GUIDES.md) before commiting changes. pavel-odintsov-fastnetmon-394fbe0/.github/FUNDING.yml000066400000000000000000000001301520703010000224110ustar00rootroot00000000000000# These are supported funding model platforms custom: ['https://fastnetmon.com/price/'] pavel-odintsov-fastnetmon-394fbe0/.github/ISSUE_TEMPLATE.md000066400000000000000000000016311520703010000233100ustar00rootroot00000000000000# 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 :) pavel-odintsov-fastnetmon-394fbe0/.github/workflows/000077500000000000000000000000001520703010000226375ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/.github/workflows/check-fmt-release.yml000066400000000000000000000062011520703010000266400ustar00rootroot00000000000000name: Check for new fmt release on: schedule: # Run daily at 06:00 UTC - cron: '0 6 * * *' workflow_dispatch: permissions: contents: read issues: write jobs: check-fmt: runs-on: ubuntu-latest steps: - name: Get latest fmt release id: latest env: GH_TOKEN: ${{ github.token }} run: | latest=$(curl -fsSL \ -H "Authorization: Bearer ${GH_TOKEN}" \ -H "Accept: application/vnd.github+json" \ https://api.github.com/repos/fmtlib/fmt/releases/latest | jq -r .tag_name) if [ -z "$latest" ] || [ "$latest" = "null" ]; then echo "::error::Failed to fetch latest fmt release tag" exit 1 fi echo "latest=$latest" >> "$GITHUB_OUTPUT" echo "Latest fmt release: $latest" - name: Check for existing issue id: existing env: GH_TOKEN: ${{ github.token }} LATEST: ${{ steps.latest.outputs.latest }} REPO: ${{ github.repository }} run: | title="New fmt release available: ${LATEST}" # Search both open and closed issues so we do not reopen / duplicate # an already-acknowledged release notification. query=$(jq -rn --arg repo "$REPO" --arg title "$title" \ '"repo:\($repo) is:issue in:title \"\($title)\""') count=$(curl -fsSL -G \ -H "Authorization: Bearer ${GH_TOKEN}" \ -H "Accept: application/vnd.github+json" \ --data-urlencode "q=${query}" \ https://api.github.com/search/issues | jq -r '.total_count') echo "Matching issues found: $count" echo "count=$count" >> "$GITHUB_OUTPUT" echo "title=$title" >> "$GITHUB_OUTPUT" - name: Create issue for new fmt release if: steps.existing.outputs.count == '0' env: GH_TOKEN: ${{ github.token }} LATEST: ${{ steps.latest.outputs.latest }} REPO: ${{ github.repository }} TITLE: ${{ steps.existing.outputs.title }} run: | body=$(cat <> "$GITHUB_OUTPUT" echo "Bundled nlohmann/json version: $bundled" - name: Get latest nlohmann/json release id: latest env: GH_TOKEN: ${{ github.token }} run: | tag=$(curl -fsSL \ -H "Authorization: Bearer ${GH_TOKEN}" \ -H "Accept: application/vnd.github+json" \ https://api.github.com/repos/nlohmann/json/releases/latest | jq -r .tag_name) if [ -z "$tag" ] || [ "$tag" = "null" ]; then echo "::error::Failed to fetch latest nlohmann/json release tag" exit 1 fi # Release tags look like "v3.11.3"; strip the leading "v" for comparison. version="${tag#v}" echo "tag=$tag" >> "$GITHUB_OUTPUT" echo "version=$version" >> "$GITHUB_OUTPUT" echo "Latest nlohmann/json release: $tag (version $version)" - name: Up to date if: steps.latest.outputs.version == steps.bundled.outputs.bundled run: echo "Bundled nlohmann/json (${{ steps.bundled.outputs.bundled }}) matches the latest release; nothing to do." - name: Download new json.hpp from upstream release if: steps.latest.outputs.version != steps.bundled.outputs.bundled env: TAG: ${{ steps.latest.outputs.tag }} run: | url="https://github.com/nlohmann/json/releases/download/${TAG}/json.hpp" echo "Downloading $url" # Download to a temporary file, then verify the version matches before # overwriting the bundled header. tmp=$(mktemp) curl -fsSL --retry 3 -o "$tmp" "$url" # Sanity check: ensure the downloaded file contains the expected version macros. new_major=$(grep -E '^#define NLOHMANN_JSON_VERSION_MAJOR' "$tmp" | awk '{print $3}') new_minor=$(grep -E '^#define NLOHMANN_JSON_VERSION_MINOR' "$tmp" | awk '{print $3}') new_patch=$(grep -E '^#define NLOHMANN_JSON_VERSION_PATCH' "$tmp" | awk '{print $3}') new_version="${new_major}.${new_minor}.${new_patch}" expected="${TAG#v}" if [ "$new_version" != "$expected" ]; then echo "::error::Downloaded json.hpp version ($new_version) does not match release tag ($expected)" exit 1 fi mv "$tmp" src/nlohmann/json.hpp echo "Replaced src/nlohmann/json.hpp with upstream $TAG" - name: Create pull request if: steps.latest.outputs.version != steps.bundled.outputs.bundled uses: peter-evans/create-pull-request@v6 with: token: ${{ github.token }} branch: chore/update-nlohmann-json-${{ steps.latest.outputs.version }} delete-branch: true commit-message: | Update bundled nlohmann/json to ${{ steps.latest.outputs.version }} Replaces src/nlohmann/json.hpp with the upstream single-header release from https://github.com/nlohmann/json/releases/tag/${{ steps.latest.outputs.tag }}. title: "Update bundled nlohmann/json to ${{ steps.latest.outputs.version }}" body: | Automated update of the bundled [nlohmann/json](https://github.com/nlohmann/json) single-header library. - Previous version: `${{ steps.bundled.outputs.bundled }}` - New version: `${{ steps.latest.outputs.version }}` (tag `${{ steps.latest.outputs.tag }}`) - Source: https://github.com/nlohmann/json/releases/download/${{ steps.latest.outputs.tag }}/json.hpp - Release notes: https://github.com/nlohmann/json/releases/tag/${{ steps.latest.outputs.tag }} The downloaded `json.hpp` was verified to advertise version `${{ steps.latest.outputs.version }}` via its `NLOHMANN_JSON_VERSION_*` macros before being committed. _Opened automatically by the `update-nlohmann-json` workflow._ labels: dependencies author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> pavel-odintsov-fastnetmon-394fbe0/.gitignore000066400000000000000000000011001520703010000212220ustar00rootroot00000000000000*.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 pavel-odintsov-fastnetmon-394fbe0/.gitmodules000066400000000000000000000001761520703010000214230ustar00rootroot00000000000000[submodule "src/juniper_plugin/netconf"] path = src/juniper_plugin/netconf url = https://github.com/Juniper/netconf-php.git pavel-odintsov-fastnetmon-394fbe0/LICENSE000066400000000000000000000431721520703010000202560ustar00rootroot00000000000000GNU 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. pavel-odintsov-fastnetmon-394fbe0/README.md000066400000000000000000000127121520703010000205240ustar00rootroot00000000000000![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/) 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) pavel-odintsov-fastnetmon-394fbe0/SECURITY.md000066400000000000000000000007411520703010000210350ustar00rootroot00000000000000# 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 email pavel.odintsov@gmail.com We guarantee initial feedback in 48 hours. pavel-odintsov-fastnetmon-394fbe0/THANKS.md000066400000000000000000000041371520703010000205610ustar00rootroot00000000000000Thanks 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 pavel-odintsov-fastnetmon-394fbe0/docs/000077500000000000000000000000001520703010000201725ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/docs/images/000077500000000000000000000000001520703010000214375ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/docs/images/deploy.png000066400000000000000000006026131520703010000234510ustar00rootroot00000000000000PNG  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`pavel-odintsov-fastnetmon-394fbe0/docs/images/network_map.png000066400000000000000000000767171520703010000245150ustar00rootroot00000000000000PNG  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`pavel-odintsov-fastnetmon-394fbe0/src/000077500000000000000000000000001520703010000200315ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/.clang-format000066400000000000000000000037731520703010000224160ustar00rootroot00000000000000--- 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 ... pavel-odintsov-fastnetmon-394fbe0/src/.clang_formatter_excludes000066400000000000000000000011241520703010000250730ustar00rootroot00000000000000netmap_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 pavel-odintsov-fastnetmon-394fbe0/src/CMakeLists.txt000066400000000000000000001500001520703010000225650ustar00rootroot00000000000000# 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 9) 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) # IPFIX collector as separate module add_library(ipfix_collector STATIC netflow_plugin/ipfix_collector.cpp) target_link_libraries(ipfix_collector ipfix_rfc) # Netflow v9 collector as separate module add_library(netflow_v9_collector STATIC netflow_plugin/netflow_v9_collector.cpp) # Netflow v5 collector as separate module add_library(netflow_v5_collector STATIC netflow_plugin/netflow_v5_collector.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_collector netflow_v9_collector netflow_v5_collector 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 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_package(bson QUIET) find_package(mongoc QUIET) if (bson_FOUND AND mongoc_FOUND) message(STATUS "We found mongo-c library mongoc::mongoc bson::bson") add_definitions(-DMONGO -DUSING_MONGO2) target_link_libraries(fastnetmon mongoc::mongoc) target_link_libraries(fastnetmon_logic mongoc::mongoc) else() ### 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() 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 "github.com/pavel-odintsov/fastnetmon") 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() pavel-odintsov-fastnetmon-394fbe0/src/Dockerfile000066400000000000000000000010771520703010000220300ustar00rootroot00000000000000FROM 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"] pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/000077500000000000000000000000001520703010000217705ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/.gitignore000066400000000000000000000000421520703010000237540ustar00rootroot00000000000000*.pyc *.python *.egg *.egg-info/ pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/README.md000066400000000000000000000101161520703010000232460ustar00rootroot00000000000000# 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. pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/a10.py000077500000000000000000000030701520703010000227260ustar00rootroot00000000000000 # # 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 pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/change_log.txt000066400000000000000000000006441520703010000246230ustar00rootroot00000000000000Change 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 pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/configs/000077500000000000000000000000001520703010000234205ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/configs/README.md000066400000000000000000000022641520703010000247030ustar00rootroot00000000000000# 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. pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/configs/tps_base_config_v1.txt000066400000000000000000000027501520703010000277200ustar00rootroot00000000000000system 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 pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/configs/tps_zone_config_v1.txt000066400000000000000000000063621520703010000277640ustar00rootroot00000000000000ddos 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 pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/fastnetmon_a10_v0.3.py000077500000000000000000000047131520703010000257370ustar00rootroot00000000000000#!/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) pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/json_configs/000077500000000000000000000000001520703010000244515ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/json_configs/__init__.py000066400000000000000000000000001520703010000265500ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/json_configs/bgp.py000066400000000000000000000005271520703010000255770ustar00rootroot00000000000000bgp_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 pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/json_configs/ddos_dst_zone.py000066400000000000000000000252441520703010000276700ustar00rootroot00000000000000 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 pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/json_configs/ddos_dst_zone_backup.py000066400000000000000000000021171520703010000312070ustar00rootroot00000000000000 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 pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/json_configs/logoff.py000066400000000000000000000000431520703010000262740ustar00rootroot00000000000000 logoff_path = '/axapi/v3/logoff' pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/json_configs/tps_base_config_json_v1.txt000066400000000000000000000101761520703010000320030ustar00rootroot00000000000000a10-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 } ], } } pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/json_configs/tps_zone_config_json_v1.txt000066400000000000000000000250551520703010000320460ustar00rootroot00000000000000a10-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, } ] } } ] } pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/json_configs/write_memory.py000066400000000000000000000000521520703010000275420ustar00rootroot00000000000000write_mem_path = '/axapi/v3/write/memory' pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/tests/000077500000000000000000000000001520703010000231325ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/tests/README.md000066400000000000000000000022211520703010000244060ustar00rootroot00000000000000## 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$ ``` pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/tests/__init__.py000066400000000000000000000000001520703010000252310ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/a10_plugin/tests/helperTests.py000066400000000000000000000025141520703010000260100ustar00rootroot00000000000000import 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() pavel-odintsov-fastnetmon-394fbe0/src/abstract_subnet_counters.hpp000066400000000000000000000235111520703010000256510ustar00rootroot00000000000000#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)); } }; pavel-odintsov-fastnetmon-394fbe0/src/actions/000077500000000000000000000000001520703010000214715ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/actions/exabgp_action.cpp000066400000000000000000000046601520703010000250060ustar00rootroot00000000000000#include "exabgp_action.hpp" #include // Migrate to STL after migration to GCC 13+ #define FMT_HEADER_ONLY #include "../fmt/compile.h" #include "../fmt/format.h" #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) { std::string bgp_message; if (action == "ban") { // Migrate fmt::format to std::format after migration to GCC 13+ bgp_message = fmt::format("announce route {} next-hop {} community {}\n", prefix_as_string_with_mask, exabgp_next_hop, exabgp_community); } else { bgp_message = fmt::format("withdraw route {} next-hop {}\n", prefix_as_string_with_mask, exabgp_next_hop); } 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.c_str(), bgp_message.size()); if (wrote_bytes != bgp_message.size()) { 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); } } pavel-odintsov-fastnetmon-394fbe0/src/actions/exabgp_action.hpp000066400000000000000000000002661520703010000250110ustar00rootroot00000000000000#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); pavel-odintsov-fastnetmon-394fbe0/src/actions/gobgp_action.cpp000066400000000000000000000326141520703010000246360ustar00rootroot00000000000000#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); } } pavel-odintsov-fastnetmon-394fbe0/src/actions/gobgp_action.hpp000066400000000000000000000006341520703010000246400ustar00rootroot00000000000000#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); pavel-odintsov-fastnetmon-394fbe0/src/afpacket_plugin/000077500000000000000000000000001520703010000231655ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/afpacket_plugin/afpacket_collector.cpp000066400000000000000000000526611520703010000275270ustar00rootroot00000000000000#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; } parser_options_t parser_options{}; parser_options.unpack_gre = fastnetmon_global_configuration.af_packet_extract_tunnel_traffic; parser_options.unpack_gtp_v1 = false; parser_options.read_packet_length_from_ip_header = fastnetmon_global_configuration.af_packet_read_packet_length_from_ip_header; auto result = parse_raw_packet_to_simple_packet_full((u_char*)data_pointer, ppd->tp_snaplen, ppd->tp_snaplen, packet, parser_options); if (result != 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; } if (false) { // // We need to apply BPF program right here but we have really tricky case explained here: // https://natanyellin.com/posts/ebpf-filtering-done-right/ // When we created socket it started received *all* traffic for all interfaces without any BPF applied and we // already have traffic waiting for us in buffer If we read it right now then we will receive large burs of // non-sampled traffic which will cause enormous spike and false positive bans. // // Fortunately for us ADD_MEMBERSHIP will drain all traffic from buffer and will re-apply BPF for freshly added // interface and everything will work just fine // /* Source code for this BPF programme is afpacket_bpf_random.txt ./bpf_asm -c afpacket_bpf_random.txt bpf_asm build: wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.4.12.tar.xz tar -xf linux-6.4.12.tar.xz cd linux-6.4.12/tools/bpf sudo apt install -y binutils-dev libreadline-dev bison flex make */ // clang-format off struct sock_filter bpf_random_sampler[5] = { { 0x20, 0, 0, 0xfffff038 }, { 0x94, 0, 0, 0x00000004 }, // Sampling rate encoded here { 0x15, 0, 1, 0x00000001 }, { 0x06, 0, 0, 0xffffffff }, { 0x06, 0, 0, 0000000000 }, }; // clang-format on // Configure sampling rate bpf_random_sampler[1].k = uint32_t(fastnetmon_global_configuration.mirror_af_packet_sampling_rate); struct sock_fprog bpf_programm; // We need number of elements in bpf_random_sampler array bpf_programm.len = 5; bpf_programm.filter = bpf_random_sampler; // It was added in Linux Kernel 3.16: https://pavel.network/linux-and-bpf-random-opcode/ // It was part of this patch: https://www.spinics.net/lists/netdev/msg279779.html // Documentation: https://www.kernel.org/doc/Documentation/networking/filter.txt (look for random) int attach_filter_result = setsockopt(packet_socket, SOL_SOCKET, SO_ATTACH_FILTER, &bpf_programm, sizeof(bpf_programm)); if (attach_filter_result != 0) { logger << log4cpp::Priority::ERROR << "Can't attach BPF filter for interface " << interface_name << " errno: " << errno << " error: " << strerror(errno); // Human readable error to provide more details about what's going on logger << log4cpp::Priority::ERROR << "For some reasons BPF sampling is not supported on your platform"; // Retrieve kernel version to provide more detailed errors std::string kernel_version; if (!get_kernel_version(kernel_version)) { logger << log4cpp::Priority::ERROR << "Cannot get kernel version"; return false; } if (kernel_version.starts_with("3.10.")) { logger << log4cpp::Priority::ERROR << "You run CentOS 7 and BPF sampling is not supported on your kernel"; logger << log4cpp::Priority::ERROR << "You can disable sampling or upgrade to recent version of your Linux distribution"; } return false; } logger << log4cpp::Priority::INFO << "Configured BPF sampling filter with rate: " << fastnetmon_global_configuration.mirror_af_packet_sampling_rate << " for " << interface_name; } // 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(); } } pavel-odintsov-fastnetmon-394fbe0/src/afpacket_plugin/afpacket_collector.hpp000066400000000000000000000004371520703010000275260ustar00rootroot00000000000000#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(); pavel-odintsov-fastnetmon-394fbe0/src/all_logcpp_libraries.hpp000066400000000000000000000006361520703010000247170ustar00rootroot00000000000000#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 pavel-odintsov-fastnetmon-394fbe0/src/api.cpp000066400000000000000000000460411520703010000213130ustar00rootroot00000000000000#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; } pavel-odintsov-fastnetmon-394fbe0/src/api.hpp000066400000000000000000000035271520703010000213220ustar00rootroot00000000000000#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; }; pavel-odintsov-fastnetmon-394fbe0/src/attack_details.hpp000066400000000000000000000040551520703010000235220ustar00rootroot00000000000000#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; pavel-odintsov-fastnetmon-394fbe0/src/ban_list.hpp000066400000000000000000000107101520703010000223340ustar00rootroot00000000000000#pragma once // 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, const 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(const 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, attack_details_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; } // Get blackhole details by UUID bool get_blackhole_details_by_uuid(const boost::uuids::uuid& mitigation_uuid, TemplateKeyType& client_id, attack_details_t& banlist_item) { 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; banlist_item = itr->second; return true; } // Returns number of blocked elements size_t get_number_of_blocked_entries() { std::lock_guard lock_guard(structure_mutex); return ban_list_storage.size(); } private: std::map ban_list_storage; std::mutex structure_mutex; }; pavel-odintsov-fastnetmon-394fbe0/src/bgp_protocol.cpp000066400000000000000000000654731520703010000232450ustar00rootroot00000000000000#include "bgp_protocol.hpp" #include #include "fast_library.hpp" #include "network_data_structures.hpp" #include #include #include #include #include #include #include #include "nlohmann/json.hpp" // 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) { if (len == 0 or value == NULL) { logger << log4cpp::Priority::WARN << "NLRI content is blank for this announce"; return false; } // Here we have prefix length in CIDR notation, e.g. 24 for /24 subnet uint8_t prefix_cidr_length = value[0]; // We cap it by /32 as we deal with IPv4 only here if (prefix_cidr_length > 32) { logger << log4cpp::Priority::ERROR << "Too big prefix length in CIDR notation: " << int(prefix_cidr_length); return false; } // Calculate how many bytes we need to store this subnet uint32_t prefix_byte_length = how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_cidr_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; // As we explicitly capped prefix_cidr_length by 32 we will never have more than 4 bytes for prefix storage uint32_t prefix_ipv4 = 0; memcpy(&prefix_ipv4, value + sizeof(prefix_cidr_length), 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(prefix_cidr_length); // Remove useless bits with this approach prefix_ipv4 = prefix_ipv4 & subnet_address_netmask_binary; extracted_prefix.subnet_address = prefix_ipv4; extracted_prefix.cidr_prefix_length = prefix_cidr_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_attribute_to_ipv6_announce((uint8_t*)binary_attribute.get_pointer(), binary_attribute.get_used_size(), 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_attribute_to_ipv6_announce(uint8_t* data, int length, bgp_attibute_common_header_t bgp_attibute_common_header, IPv6UnicastAnnounce& ipv6_announce) { const uint8_t* mp_reach_attribute_shift = data + bgp_attibute_common_header.attribute_body_shift; // Handy for sanity checks const uint8_t* buffer_end = data + length; if (mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t) > buffer_end) { logger << log4cpp::Priority::ERROR << "Not enough data for reading MP Reach NLRI attribute header"; return false; } // 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; } // https://www.rfc-editor.org/rfc/rfc2545?utm_source=chatgpt.com // The value of the Length of Next Hop Network Address field on a // MP_REACH_NLRI attribute shall be set to 16, when only a global // address is present, or 32 if a link-local address is also included in // the Next Hop field. if (bgp_mp_ext_header->length_of_next_hop != 16 && bgp_mp_ext_header->length_of_next_hop != 32) { logger << log4cpp::Priority::WARN << "We support only 16 or 32 byte next hop for IPv6 MP Reach NLRI but got " << int(bgp_mp_ext_header->length_of_next_hop) << " byte long next hop"; 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 // Please note that we ensure that we can read both 16 or 32 bytes of next hop data with this check if (mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t) + bgp_mp_ext_header->length_of_next_hop > buffer_end) { logger << log4cpp::Priority::ERROR << "Not enough data for reading IPv6 next hop in MP Reach NLRI attribute"; return false; } // Read only global IPv6 address which is 16 byte long memcpy(&next_hop_ipv6.subnet_address, mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t), 16); ipv6_announce.set_next_hop(next_hop_ipv6); // In case of 32 byte long next hop we can read link-local address which placed right after global IPv6 address if (bgp_mp_ext_header->length_of_next_hop == 32) { // We read it but we do not store it anywhere else due to no customer demand memcpy(&next_hop_ipv6.subnet_address, mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t) + sizeof(next_hop_ipv6.subnet_address), 16); // logger << log4cpp::Priority::INFO << "Got link-local next hop in MP Reach NLRI attribute: " << convert_ipv6_subnet_to_string(next_hop_ipv6) << std::endl; } // logger << log4cpp::Priority::INFO << "IPv6 next hop is: "<< convert_ipv6_subnet_to_string(next_hop_ipv6); // Check that we have enough data for SNPA bit if (mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t) + bgp_mp_ext_header->length_of_next_hop + sizeof(uint8_t) > buffer_end) { logger << log4cpp::Priority::ERROR << "Not enough data for reading SNPA bit in MP Reach NLRI attribute"; return false; } const uint8_t* snpa_bit = mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t) + bgp_mp_ext_header->length_of_next_hop; // It's expected to be zero all the time but when it's not we just warn about it if (*snpa_bit != 0) { logger << log4cpp::Priority::ERROR << "SNPA bit in MP Reach NLRI attribute is not zero as expected but " << int(*snpa_bit); } // Check that we have enough data for prefix length field if (mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t) + bgp_mp_ext_header->length_of_next_hop + sizeof(uint8_t) + sizeof(uint8_t) > buffer_end) { logger << log4cpp::Priority::ERROR << "Not enough data for reading prefix length field in MP Reach NLRI attribute"; return false; } const 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); subnet_ipv6_cidr_mask_t prefix_ipv6; prefix_ipv6.cidr_prefix_length = *prefix_length; if (prefix_ipv6.cidr_prefix_length > 128) { logger << log4cpp::Priority::ERROR << "Too big prefix length in CIDR notation: " << int(prefix_ipv6.cidr_prefix_length); return false; } // 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"; // Ensure that we have enough data for prefix itself if (mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t) + bgp_mp_ext_header->length_of_next_hop + sizeof(uint8_t) + sizeof(uint8_t) + number_of_bytes_required_for_prefix > buffer_end) { logger << log4cpp::Priority::ERROR << "Not enough data for reading prefix itself in MP Reach NLRI attribute"; return false; } // 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_attributes_to_ipv4_announce(char* value, int len, 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::ERROR << "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::ERROR << "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: { if (logger.getPriority() == log4cpp::Priority::DEBUG) { 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: { if (logger.getPriority() == log4cpp::Priority::DEBUG) { 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: { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Got BGP_ATTRIBUTE_LOCAL_PREF but I do not have code for parsing it"; } } break; case BGP_ATTRIBUTE_COMMUNITY: { if (bgp_attibute_common_header.attribute_value_length % sizeof(bgp_community_attribute_element_t) != 0) { logger << log4cpp::Priority::WARN << "BGP_ATTRIBUTE_COMMUNITY length " << bgp_attibute_common_header.attribute_value_length << " is not a multiple of community element size"; return false; } uint32_t number_of_communities = bgp_attibute_common_header.attribute_value_length / sizeof(bgp_community_attribute_element_t); uint8_t* community_data = (uint8_t*)value + bgp_attibute_common_header.attribute_flag_and_type_length + bgp_attibute_common_header.length_of_length_field; for (uint32_t i = 0; i < number_of_communities; i++) { bgp_community_attribute_element_t community_element; memcpy(&community_element, community_data + i * sizeof(bgp_community_attribute_element_t), sizeof(community_element)); // Convert from network byte order to host byte order // TODO: it's REALLY BAD as we use both endian less versions of this strucutre in code // We store it in little endian and then convert in place to big endian when push to wire community_element.asn_number = fast_ntoh(community_element.asn_number); community_element.community_number = fast_ntoh(community_element.community_number); unicast_ipv4_announce.add_community(community_element); } } 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: { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "BGP_ATTRIBUTE_EXTENDED_COMMUNITY with length: " << bgp_attibute_common_header.attribute_value_length; } } break; case BGP_ATTRIBUTE_ORIGINATOR_ID: { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "BGP_ATTRIBUTE_ORIGINATOR_ID with length: " << bgp_attibute_common_header.attribute_value_length; } } break; case BGP_ATTRIBUTE_CLUSTER_LIST: { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "BGP_ATTRIBUTE_CLUSTER_LIST with length: " << bgp_attibute_common_header.attribute_value_length; } } break; default: { if (logger.getPriority() == log4cpp::Priority::DEBUG) { 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_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(); if (logger.getPriority() == log4cpp::Priority::DEBUG) { 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(); if (logger.getPriority() == log4cpp::Priority::DEBUG) { 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); if (logger.getPriority() == log4cpp::Priority::DEBUG) { 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); if (logger.getPriority() == log4cpp::Priority::DEBUG) { 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_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_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(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_cidr_length) { return ceil(float(prefix_cidr_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; } pavel-odintsov-fastnetmon-394fbe0/src/bgp_protocol.hpp000066400000000000000000000746371520703010000232540ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "dynamic_binary_buffer.hpp" #include "fast_endianless.hpp" #include "fast_library.hpp" #include "fastnetmon_networks.hpp" #include #include "iana/iana_ip_protocols.hpp" class bgp_attribute_origin; class bgp_attribute_next_hop_ipv4; class IPv4UnicastAnnounce; class IPv6UnicastAnnounce; extern log4cpp::Category& logger; 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); 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"); // 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 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; } if (length_of_length_field == 1) { // Read one byte attribute_value_length = value[2]; } else if (length_of_length_field == 2) { // Read two bytes attribute_value_length = fast_ntoh(*(uint16_t*)(value + 2)); } else { logger << log4cpp::Priority::ERROR << "Broken length of length field: " << length_of_length_field; return false; } uint32_t total_attribute_length = attribute_flag_and_type_length + length_of_length_field + 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_attributes_to_ipv4_announce(char* value, int len, 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://datatracker.ietf.org/doc/html/rfc4456#page-6 BGP_ATTRIBUTE_ORIGINATOR_ID = 9, // https://datatracker.ietf.org/doc/html/rfc4456#page-6 BGP_ATTRIBUTE_CLUSTER_LIST = 10, // 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 = fast_ntoh(afi_identifier); } void host_byte_order_to_network_byte_order() { afi_identifier = fast_hton(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; bool operator==(const bgp_community_attribute_element_t& other) const { return asn_number == other.asn_number && community_number == other.community_number; } void host_byte_order_to_network_byte_order() { asn_number = htons(asn_number); community_number = htons(community_number); } }; bool encode_bgp_flow_spec_elements_into_bgp_mp_attributebgp_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() const { 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); } bool get_attributes(std::vector& attributes_list) 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; origin_attr.attribute_value = static_cast(this->origin); // logger << log4cpp::Priority::WARN << "origin_attr: " << // origin_attr.print() << // std::endl; dynamic_binary_buffer_t origin_as_binary_array; origin_as_binary_array.set_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_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 // We expect that we receive empty vector here attributes_list.clear(); // 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; uint64_t long_attribute_length = sizeof(bgp_as_path_segment_element_t) + this->as_path_asns.size() * sizeof(uint32_t); // TODO: We need to enable extended length encoding when we have more than 255 bytes for attribute value if (long_attribute_length > 255) { logger << log4cpp::Priority::ERROR << "Too big AS_PATH attribute length: " << long_attribute_length; return false; } // Populate attribute length bgp_attribute_as_path.attribute_length = (uint8_t)long_attribute_length; 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_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 number: " << 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 true; } // 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_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) { // TODO: Encode they in network byte order // That's HORRIBLE and very error prone approach 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 true; } 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; } 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: uint32_t next_hop = 0; subnet_cidr_mask_t prefix; BGP_ORIGIN_TYPES origin = BGP_ORIGIN_INCOMPLETE; bool is_withdraw = false; // TODO: it's horrible encoding idea // We store data here in little endian and then convert it into network byte order when need to send it to GoBGP 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; // TODO: it's horrible encoding idea // We store data here in little endian and then convert it into network byte order when need to send it to GoBGP 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_attribute_to_ipv6_announce(uint8_t* data, int length, 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); bool read_bgp_community_from_string(std::string community_as_string, bgp_community_attribute_element_t& bgp_community_attribute_element);pavel-odintsov-fastnetmon-394fbe0/src/bgp_protocol_flow_spec.cpp000066400000000000000000002706711520703010000253040ustar00rootroot00000000000000#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" // By default use default MTU uint64_t reject_flow_spec_validation_if_slow_spec_length_exceeds_this_number = 1500; // 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 decode_native_flow_spec_announce_from_binary_encoded_atributes(std::vector binary_attributes, flow_spec_rule_t& flow_spec_rule) { 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_attibute_common_header.print() ; if (bgp_attibute_common_header.attribute_type == BGP_ATTRIBUTE_EXTENDED_COMMUNITY) { // logger << log4cpp::Priority::WARN << "Got BGP_ATTRIBUTE_EXTENDED_COMMUNITIES with length " << // gobgp_lib_path->path_attributes[i]->len ; // TODO: TBD // logger << log4cpp::Priority::WARN << bgp_attibute_common_header.print() ; uint32_t number_of_extened_community_elements = bgp_attibute_common_header.attribute_value_length / sizeof(bgp_extended_community_element_t); if (bgp_attibute_common_header.attribute_value_length % sizeof(bgp_extended_community_element_t) != 0) { logger << log4cpp::Priority::WARN << "attribute_value_length should be multiplied by " << sizeof(bgp_extended_community_element_t) << " bytes"; return false; } // logger << log4cpp::Priority::WARN << "We have: " << number_of_extened_community_elements << " // extended community elements" ; if (number_of_extened_community_elements != 1) { logger << log4cpp::Priority::WARN << "We do not support multiple or zero extended communities " "for flow spec announes"; return false; } uint8_t* attribute_shift = (uint8_t*)binary_attribute.get_pointer() + bgp_attibute_common_header.attribute_body_shift; // TODO: we could read only first community element bgp_extended_community_element_t* bgp_extended_community_element = (bgp_extended_community_element_t*)attribute_shift; // logger << log4cpp::Priority::WARN << bgp_extended_community_element->print() ; // This type for BGP flow spec actions if (bgp_extended_community_element->type_hight == EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL) { if (bgp_extended_community_element->type_low == FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_RATE) { // logger << log4cpp::Priority::WARN << "Found flow spec action value!!!" ; // Two bytes in value store identification. It's not useful for us and // we could drop it // Next 4 bytes has float value encoded as IEEE.754.1985. So we could // use float (4 bytes too) to interpret it uint32_t* rate_as_integer = (uint32_t*)(bgp_extended_community_element->value + 2); // Yes, really, float number encoded as big endian and we should decode // it *rate_as_integer = ntohl(*rate_as_integer); float* rate_as_float_ptr = (float*)rate_as_integer; if (*rate_as_float_ptr < 0) { logger << log4cpp::Priority::WARN << "Rate could not be negative"; return false; } bgp_flow_spec_action_t bgp_flow_spec_action; if (int(*rate_as_float_ptr) == 0) { bgp_flow_spec_action.set_type(bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_DISCARD); } else { bgp_flow_spec_action.set_type(bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT); bgp_flow_spec_action.set_rate_limit(int(*rate_as_float_ptr)); } flow_spec_rule.set_action(bgp_flow_spec_action); // logger << log4cpp::Priority::WARN << "Rate: " << *rate_as_float_ptr ; } else if (bgp_extended_community_element->type_low == FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_REDIRECT_AS_TWO_BYTE) { const redirect_2_octet_as_4_octet_value_t* redirect_2_octet_as_4_octet_value = (const redirect_2_octet_as_4_octet_value_t*)bgp_extended_community_element->value; bgp_flow_spec_action_t bgp_flow_spec_action; bgp_flow_spec_action.set_type(bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT); bgp_flow_spec_action.set_redirect_as(redirect_2_octet_as_4_octet_value->get_as_host_byte_order()); bgp_flow_spec_action.set_redirect_value(redirect_2_octet_as_4_octet_value->get_value_host_byte_order()); flow_spec_rule.set_action(bgp_flow_spec_action); logger << log4cpp::Priority::DEBUG << "BGP Flow Spec redirect field: " << redirect_2_octet_as_4_octet_value->print(); } else if (bgp_extended_community_element->type_low == FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_REMARKING) { logger << log4cpp::Priority::WARN << "BGP Flow Spec traffic marking is not supported: " << bgp_extended_community_element->print(); } } } else if (bgp_attibute_common_header.attribute_type == BGP_ATTRIBUTE_MP_REACH_NLRI) { // logger << log4cpp::Priority::WARN << "Will process BGP_ATTRIBUTE_MP_REACH_NLRI in details" << // std::endl; // logger << log4cpp::Priority::WARN << "Whole MP reach NLRI in hex: " << // print_binary_string_as_hex_without_leading_0x( // (uint8_t*)gobgp_lib_path->path_attributes[i]->value, // gobgp_lib_path->path_attributes[i]->len) ; uint8_t* flow_spec_attribute_shift = (uint8_t*)binary_attribute.get_pointer() + bgp_attibute_common_header.attribute_body_shift; bgp_mp_ext_flow_spec_header_t* bgp_mp_ext_flow_spec_header = (bgp_mp_ext_flow_spec_header_t*)flow_spec_attribute_shift; bgp_mp_ext_flow_spec_header->network_to_host_byte_order(); if (not(bgp_mp_ext_flow_spec_header->afi_identifier == AFI_IP and bgp_mp_ext_flow_spec_header->safi_identifier == SAFI_FLOW_SPEC_UNICAST)) { logger << log4cpp::Priority::WARN << "We have got unexpected AFI or SAFI numbers from BGP Flow " "Spec MP header"; return false; } uint8_t* flow_spec_types_shift = (uint8_t*)binary_attribute.get_pointer() + bgp_attibute_common_header.attribute_body_shift + sizeof(bgp_mp_ext_flow_spec_header_t); // logger << log4cpp::Priority::WARN << bgp_mp_ext_flow_spec_header->print() ; uint16_t nlri_value_length = 0; uint16_t nlri_length_of_length_field = 1; // 240 is 0xf0 if (flow_spec_types_shift[0] < 240) { nlri_value_length = flow_spec_types_shift[0]; nlri_length_of_length_field = 1; } else { nlri_length_of_length_field = 2; logger << log4cpp::Priority::WARN << "We do not support for 2 byte NLRI length encoding yet"; return false; } // TODO: add sanity checks for length // logger << log4cpp::Priority::WARN << "We have " << // uint32_t(gobgp_lib_path->path_attributes[i]->len) << " byte length // attrinute" ; // logger << log4cpp::Priority::WARN << "We have NLRI header length: " << // uint32_t(sizeof(bgp_mp_ext_flow_spec_header_t)) ; // logger << log4cpp::Priority::WARN << "We have " << uint32_t(nlri_value_length) << " byte length // NLRI" ; bool flowspec_decode_result = flow_spec_decode_nlri_value((uint8_t*)(flow_spec_types_shift + nlri_length_of_length_field), nlri_value_length, flow_spec_rule); if (!flowspec_decode_result) { logger << log4cpp::Priority::WARN << "Could not parse Flow Spec payload"; return false; } } } return true; } // Build BGP attributes for BGP flow spec announce std::vector build_attributes_for_flowspec_announce(flow_spec_rule_t flow_spec_rule) { // Prepare origin bgp_attribute_origin origin_attr; dynamic_binary_buffer_t origin_as_binary_array; origin_as_binary_array.set_buffer_size_in_bytes(sizeof(origin_attr)); origin_as_binary_array.append_data_as_object_ptr(&origin_attr); dynamic_binary_buffer_t bgp_mp_ext_flow_spec_header_as_binary_array; bool encode_bgp_flow_spec_as_mp_attr_result = encode_bgp_flow_spec_elements_into_bgp_mp_attribute(flow_spec_rule, bgp_mp_ext_flow_spec_header_as_binary_array, true); if (!encode_bgp_flow_spec_as_mp_attr_result) { logger << log4cpp::Priority::WARN << "Could not encode flow spec announce as mp attribute"; return std::vector{}; } // Prepare extended community bgp_flow_spec_action_t bgp_flow_spec_action = flow_spec_rule.get_action(); if (bgp_flow_spec_action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_ACCEPT) { logger << log4cpp::Priority::DEBUG << "this flow spec rule has action set to accept, skip rate limit section"; // According to RFC: The default action for a traffic filtering flow specification is to // accept IP traffic that matches that particular rule. // And we do not need any additional communities in this case to encode action // Here we can add next hop for IPv4 when it set if (flow_spec_rule.ipv4_nexthops.size() > 0) { logger << log4cpp::Priority::DEBUG << "We have got nexthop IPv4 value for flowspec, let's add it"; dynamic_binary_buffer_t extended_attributes_flow_spec_ipv4_as_binary_array; if (flow_spec_rule.ipv4_nexthops.size() > 1) { logger << log4cpp::Priority::WARN << "We support only single IPv4 next hop for flow spec"; } // We pick up only first next hop bool next_hop_encode_result = encode_bgp_flow_spec_next_hop_as_extended_attribute(flow_spec_rule.ipv4_nexthops[0], extended_attributes_flow_spec_ipv4_as_binary_array); if (!next_hop_encode_result) { logger << log4cpp::Priority::WARN << "Cannot encode IPv4 next hop for flow spec"; return std::vector{}; } return std::vector{ origin_as_binary_array, bgp_mp_ext_flow_spec_header_as_binary_array, extended_attributes_flow_spec_ipv4_as_binary_array }; } return std::vector{ origin_as_binary_array, bgp_mp_ext_flow_spec_header_as_binary_array }; } logger << log4cpp::Priority::DEBUG << "Encode rate for flow spec"; dynamic_binary_buffer_t extended_attributes_as_binary_array; bool action_encode_result = encode_bgp_flow_spec_action_as_extended_attribute(bgp_flow_spec_action, extended_attributes_as_binary_array); if (!action_encode_result) { logger << log4cpp::Priority::WARN << "Could not encode flow spec action"; // Return blank array return std::vector{}; } logger << log4cpp::Priority::DEBUG << "Successfully encoded flow spec action"; return std::vector{ origin_as_binary_array, bgp_mp_ext_flow_spec_header_as_binary_array, extended_attributes_as_binary_array }; } // Encode flow spec elements into MP NLRI bool encode_bgp_flow_spec_elements_as_mp_nlri(const flow_spec_rule_t& flow_spec_rule, dynamic_binary_buffer_t& mp_nlri_flow_spec) { mp_nlri_flow_spec.set_buffer_size_in_bytes(2048); // Encode IPv4 destination prefix if (flow_spec_rule.destination_subnet_ipv4_used) { logger << log4cpp::Priority::DEBUG << "Encode flow spec attribute destination prefix"; mp_nlri_flow_spec.append_byte(FLOW_SPEC_ENTITY_DESTINATION_PREFIX); dynamic_binary_buffer_t encoded_destination_prefix_as_binary_array; bool dest_encode_result = encode_bgp_subnet_encoding(flow_spec_rule.destination_subnet_ipv4, encoded_destination_prefix_as_binary_array); if (!dest_encode_result) { logger << log4cpp::Priority::WARN << "Could not encode FLOW_SPEC_ENTITY_DESTINATION_PREFIX"; return false; } logger << log4cpp::Priority::DEBUG << "Encoded destination subnet as " << encoded_destination_prefix_as_binary_array.get_used_size() << " bytes array"; mp_nlri_flow_spec.append_dynamic_buffer(encoded_destination_prefix_as_binary_array); } // Encode source IPv4 prefix if (flow_spec_rule.source_subnet_ipv4_used) { logger << log4cpp::Priority::DEBUG << "Encode flow spec attribute source prefix"; mp_nlri_flow_spec.append_byte(FLOW_SPEC_ENTITY_SOURCE_PREFIX); dynamic_binary_buffer_t encoded_source_prefix_as_binary_array; bool src_encode_result = encode_bgp_subnet_encoding(flow_spec_rule.source_subnet_ipv4, encoded_source_prefix_as_binary_array); if (!src_encode_result) { logger << log4cpp::Priority::WARN << "Could not encode FLOW_SPEC_ENTITY_SOURCE_PREFIX"; return false; } logger << log4cpp::Priority::DEBUG << "Encoded source subnet as " << encoded_source_prefix_as_binary_array.get_used_size() << " bytes array"; mp_nlri_flow_spec.append_dynamic_buffer(encoded_source_prefix_as_binary_array); } if (flow_spec_rule.protocols.size() > 0) { logger << log4cpp::Priority::DEBUG << "Encode flow spec attribute protocols"; mp_nlri_flow_spec.append_byte(FLOW_SPEC_ENTITY_IP_PROTOCOL); for (auto itr = flow_spec_rule.protocols.begin(); itr != flow_spec_rule.protocols.end(); ++itr) { bgp_flow_spec_operator_byte_t bgp_flow_spec_operator_byte; bgp_flow_spec_operator_byte.set_length_in_bytes(1); // We support only equal operations bgp_flow_spec_operator_byte.set_equal_bit(); // It's it's last element set end bit if (std::distance(itr, flow_spec_rule.protocols.end()) == 1) { bgp_flow_spec_operator_byte.set_end_of_list_bit(); } mp_nlri_flow_spec.append_data_as_object_ptr(&bgp_flow_spec_operator_byte); // Cast strictly typed protocol type into underlying number uint8_t protocol_number = static_cast::type>(*itr); mp_nlri_flow_spec.append_byte(protocol_number); } } if (flow_spec_rule.destination_ports.size() > 0) { logger << log4cpp::Priority::DEBUG << "Encode flow spec attribute desination ports"; mp_nlri_flow_spec.append_byte(FLOW_SPEC_ENTITY_DESTINATION_PORT); for (auto itr = flow_spec_rule.destination_ports.begin(); itr != flow_spec_rule.destination_ports.end(); ++itr) { bgp_flow_spec_operator_byte_t bgp_flow_spec_operator_byte; // In destination_ports we encode porn number with two bytes // I have not reasons to reduce amount of data here bgp_flow_spec_operator_byte.set_length_in_bytes(sizeof(*itr)); // We support only equal operations bgp_flow_spec_operator_byte.set_equal_bit(); // It's it's last element set end bit if (std::distance(itr, flow_spec_rule.destination_ports.end()) == 1) { bgp_flow_spec_operator_byte.set_end_of_list_bit(); } mp_nlri_flow_spec.append_data_as_object_ptr(&bgp_flow_spec_operator_byte); uint16_t destination_port = *itr; destination_port = htons(destination_port); mp_nlri_flow_spec.append_data_as_object_ptr(&destination_port); } } if (flow_spec_rule.source_ports.size() > 0) { logger << log4cpp::Priority::DEBUG << "Encode flow spec attribute source ports"; mp_nlri_flow_spec.append_byte(FLOW_SPEC_ENTITY_SOURCE_PORT); for (auto itr = flow_spec_rule.source_ports.begin(); itr != flow_spec_rule.source_ports.end(); ++itr) { bgp_flow_spec_operator_byte_t bgp_flow_spec_operator_byte; // In source_ports we encode porn number with two bytes // I have not reasons to reduce amount of data here bgp_flow_spec_operator_byte.set_length_in_bytes(sizeof(*itr)); // We support only equal operations bgp_flow_spec_operator_byte.set_equal_bit(); // It's it's last element set end bit if (std::distance(itr, flow_spec_rule.source_ports.end()) == 1) { bgp_flow_spec_operator_byte.set_end_of_list_bit(); } mp_nlri_flow_spec.append_data_as_object_ptr(&bgp_flow_spec_operator_byte); uint16_t source_port = *itr; source_port = htons(source_port); mp_nlri_flow_spec.append_data_as_object_ptr(&source_port); } } if (flow_spec_rule.tcp_flags.size() > 0) { logger << log4cpp::Priority::DEBUG << "Encode flow spec attribute TCP flags"; mp_nlri_flow_spec.append_byte(FLOW_SPEC_ENTITY_TCP_FLAGS); for (auto itr = flow_spec_rule.tcp_flags.begin(); itr != flow_spec_rule.tcp_flags.end(); ++itr) { bgp_flow_spec_bitmask_operator_byte_t bgp_flow_spec_operator_byte_tcp_flags; bgp_flow_spec_operator_byte_tcp_flags.set_length_in_bytes(sizeof(bgp_flowspec_one_byte_byte_encoded_tcp_flags_t)); if (std::distance(itr, flow_spec_rule.tcp_flags.end()) == 1) { bgp_flow_spec_operator_byte_tcp_flags.set_end_of_list_bit(); } // Set match bit if we asked to do it if (flow_spec_rule.set_match_bit_for_tcp_flags) { bgp_flow_spec_operator_byte_tcp_flags.set_match_bit(); } mp_nlri_flow_spec.append_data_as_object_ptr(&bgp_flow_spec_operator_byte_tcp_flags); if (!itr->we_have_least_one_flag_enabled()) { logger << log4cpp::Priority::WARN << "For some reasons we have tcp flags attribute without flags"; return false; } bgp_flowspec_one_byte_byte_encoded_tcp_flags_t bgp_flowspec_one_byte_byte_encoded_tcp_flags = return_in_one_byte_encoding(*itr); mp_nlri_flow_spec.append_data_as_object_ptr(&bgp_flowspec_one_byte_byte_encoded_tcp_flags); } } if (flow_spec_rule.packet_lengths.size() > 0) { logger << log4cpp::Priority::DEBUG << "Encode flow spec attribute packet lengths"; mp_nlri_flow_spec.append_byte(FLOW_SPEC_ENTITY_PACKET_LENGTH); for (auto itr = flow_spec_rule.packet_lengths.begin(); itr != flow_spec_rule.packet_lengths.end(); ++itr) { bgp_flow_spec_operator_byte_t bgp_flow_spec_operator_byte; // In packet_lengths we encode porn number with two bytes // I have not reasons to reduce amount of data here bgp_flow_spec_operator_byte.set_length_in_bytes(sizeof(*itr)); // We support only equal operations bgp_flow_spec_operator_byte.set_equal_bit(); // It's it's last element set end bit if (std::distance(itr, flow_spec_rule.packet_lengths.end()) == 1) { bgp_flow_spec_operator_byte.set_end_of_list_bit(); } mp_nlri_flow_spec.append_data_as_object_ptr(&bgp_flow_spec_operator_byte); uint16_t packet_length = *itr; packet_length = htons(packet_length); mp_nlri_flow_spec.append_data_as_object_ptr(&packet_length); } } if (flow_spec_rule.fragmentation_flags.size() > 0) { logger << log4cpp::Priority::DEBUG << "Encode flow spec attribute fragmentation flags"; mp_nlri_flow_spec.append_byte(FLOW_SPEC_ENTITY_FRAGMENT); for (auto itr = flow_spec_rule.fragmentation_flags.begin(); itr != flow_spec_rule.fragmentation_flags.end(); ++itr) { bgp_flow_spec_fragmentation_entity_t bgp_flow_spec_fragmentation_entity{}; bgp_flow_spec_bitmask_operator_byte_t bgp_flow_spec_operator_byte; bgp_flow_spec_operator_byte.set_length_in_bytes(sizeof(bgp_flow_spec_fragmentation_entity_t)); if (std::distance(itr, flow_spec_rule.fragmentation_flags.end()) == 1) { bgp_flow_spec_operator_byte.set_end_of_list_bit(); } // Set match bit if we asked to do it if (flow_spec_rule.set_match_bit_for_fragmentation_flags) { bgp_flow_spec_operator_byte.set_match_bit(); } mp_nlri_flow_spec.append_data_as_object_ptr(&bgp_flow_spec_operator_byte); if (*itr == flow_spec_fragmentation_types_t::FLOW_SPEC_DONT_FRAGMENT) { bgp_flow_spec_fragmentation_entity.dont_fragment = 1; } else if (*itr == flow_spec_fragmentation_types_t::FLOW_SPEC_IS_A_FRAGMENT) { bgp_flow_spec_fragmentation_entity.is_fragment = 1; } else if (*itr == flow_spec_fragmentation_types_t::FLOW_SPEC_FIRST_FRAGMENT) { bgp_flow_spec_fragmentation_entity.first_fragment = 1; } else if (*itr == flow_spec_fragmentation_types_t::FLOW_SPEC_LAST_FRAGMENT) { bgp_flow_spec_fragmentation_entity.last_fragment = 1; } else if (*itr == flow_spec_fragmentation_types_t::FLOW_SPEC_NOT_A_FRAGMENT) { // Structure without any flags enabled } else { logger << log4cpp::Priority::WARN << "Very strange packet without fragmentation options"; return false; } mp_nlri_flow_spec.append_data_as_object_ptr(&bgp_flow_spec_fragmentation_entity); } } if (mp_nlri_flow_spec.is_failed()) { logger << log4cpp::Priority::WARN << "Internal issues with mp_nlri_flow_spec binary buffer"; return false; } return true; } // Prepare BGP MP attribute for flow spec bool encode_bgp_flow_spec_elements_into_bgp_mp_attribute(const flow_spec_rule_t& flow_spec_rule, dynamic_binary_buffer_t& bgp_mp_ext_flow_spec_header_as_binary_array, bool add_preamble) { dynamic_binary_buffer_t mp_nlri_binary_buffer; bool mp_nlri_encode_result = encode_bgp_flow_spec_elements_as_mp_nlri(flow_spec_rule, mp_nlri_binary_buffer); if (!mp_nlri_encode_result) { logger << log4cpp::Priority::WARN << "call of encode_bgp_flow_spec_elements_as_mp_nlri failed"; return false; } uint8_t nlri_length = mp_nlri_binary_buffer.get_used_size(); if (nlri_length >= 240) { logger << log4cpp::Priority::WARN << "We should encode length in two bytes"; return false; } logger << log4cpp::Priority::DEBUG << "Encoded flow spec elements as MP Reach NLRI with size: " << int(nlri_length); bgp_attribute_multiprotocol_extensions_t bgp_attribute_multiprotocol_extensions; bgp_attribute_multiprotocol_extensions.attribute_length = sizeof(bgp_mp_ext_flow_spec_header_t) + sizeof(nlri_length) + mp_nlri_binary_buffer.get_used_size(); logger << log4cpp::Priority::DEBUG << "BGP MP reach attribute length: " << int(bgp_attribute_multiprotocol_extensions.attribute_length); // Prepare flow spec MP Extenstion attribute bgp_mp_ext_flow_spec_header_as_binary_array.set_buffer_size_in_bytes(2048); bgp_mp_ext_flow_spec_header_t bgp_mp_ext_flow_spec_header; bgp_mp_ext_flow_spec_header.host_byte_order_to_network_byte_order(); // For one very special GoBGP specific encoding we need capability to strip these fields if (add_preamble) { bgp_mp_ext_flow_spec_header_as_binary_array.append_data_as_object_ptr(&bgp_attribute_multiprotocol_extensions); bgp_mp_ext_flow_spec_header_as_binary_array.append_data_as_object_ptr(&bgp_mp_ext_flow_spec_header); } bgp_mp_ext_flow_spec_header_as_binary_array.append_data_as_object_ptr(&nlri_length); bgp_mp_ext_flow_spec_header_as_binary_array.append_dynamic_buffer(mp_nlri_binary_buffer); if (bgp_mp_ext_flow_spec_header_as_binary_array.is_failed()) { logger << log4cpp::Priority::WARN << "We have issues with binary buffer in flow spec crafter code"; return false; } return true; } 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) { // Allocate buffer // We use two kind of structures here: // bgp_extended_community_element_flow_spec_rate_t and bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value_t_t // As they have same size we use size from one of them extended_attributes_as_binary_array.set_buffer_size_in_bytes( sizeof(bgp_extended_community_attribute_t) + 1 * sizeof(bgp_extended_community_element_flow_spec_rate_t)); bgp_extended_community_attribute_t bgp_extended_community_attribute; bgp_extended_community_attribute.attribute_length = sizeof(bgp_extended_community_element_t); if (bgp_flow_spec_action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_DISCARD) { bgp_extended_community_element_flow_spec_rate_t bgp_extended_community_element_flow_spec_rate; logger << log4cpp::Priority::DEBUG << "We encode flow spec discard action as zero rate"; bgp_extended_community_element_flow_spec_rate.rate_limit = 0; bgp_extended_community_element_flow_spec_rate.host_byte_order_to_network_byte_order(); extended_attributes_as_binary_array.append_data_as_object_ptr(&bgp_extended_community_attribute); extended_attributes_as_binary_array.append_data_as_object_ptr(&bgp_extended_community_element_flow_spec_rate); } else if (bgp_flow_spec_action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT) { bgp_extended_community_element_flow_spec_rate_t bgp_extended_community_element_flow_spec_rate; logger << log4cpp::Priority::DEBUG << "Encode rate limit value " << bgp_flow_spec_action.get_rate_limit(); bgp_extended_community_element_flow_spec_rate.rate_limit = bgp_flow_spec_action.get_rate_limit(); bgp_extended_community_element_flow_spec_rate.host_byte_order_to_network_byte_order(); extended_attributes_as_binary_array.append_data_as_object_ptr(&bgp_extended_community_attribute); extended_attributes_as_binary_array.append_data_as_object_ptr(&bgp_extended_community_element_flow_spec_rate); } else if (bgp_flow_spec_action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT) { bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value_t_t bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value; bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value.set_redirect_as( bgp_flow_spec_action.get_redirect_as()); bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value.set_redirect_value( bgp_flow_spec_action.get_redirect_value()); extended_attributes_as_binary_array.append_data_as_object_ptr(&bgp_extended_community_attribute); extended_attributes_as_binary_array.append_data_as_object_ptr( &bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value); } else { logger << log4cpp::Priority::WARN << "We support only discard, rate limit, redirect actions"; return false; } return true; } bool encode_bgp_flow_spec_next_hop_as_extended_attribute(uint32_t next_hop_ipv4, dynamic_binary_buffer_t& extended_attributes_as_binary_array) { // Allocate buffer extended_attributes_as_binary_array.set_buffer_size_in_bytes( sizeof(bgp_extended_community_attribute_t) + sizeof(bgp_extended_community_element_flow_spec_ipv4_next_hop_t)); bgp_extended_community_attribute_t bgp_extended_community_attribute; bgp_extended_community_attribute.attribute_length = sizeof(bgp_extended_community_element_t); // Set next hop value for structure bgp_extended_community_element_flow_spec_ipv4_next_hop_t bgp_extended_community_element_flow_spec_next_hop_ipv4; bgp_extended_community_element_flow_spec_next_hop_ipv4.next_hop_ipv4 = next_hop_ipv4; // Well, it does nothing but we can do it anyway for consistency bgp_extended_community_element_flow_spec_next_hop_ipv4.host_byte_order_to_network_byte_order(); extended_attributes_as_binary_array.append_data_as_object_ptr(&bgp_extended_community_attribute); extended_attributes_as_binary_array.append_data_as_object_ptr(&bgp_extended_community_element_flow_spec_next_hop_ipv4); return true; } std::string get_flow_spec_type_name_by_number(uint8_t flow_spec_type) { switch (flow_spec_type) { case FLOW_SPEC_ENTITY_DESTINATION_PREFIX: return "FLOW_SPEC_ENTITY_DESTINATION_PREFIX"; break; case FLOW_SPEC_ENTITY_SOURCE_PREFIX: return "FLOW_SPEC_ENTITY_SOURCE_PREFIX"; break; case FLOW_SPEC_ENTITY_IP_PROTOCOL: return "FLOW_SPEC_ENTITY_IP_PROTOCOL"; break; case FLOW_SPEC_ENTITY_PORT: return "FLOW_SPEC_ENTITY_PORT"; break; case FLOW_SPEC_ENTITY_DESTINATION_PORT: return "FLOW_SPEC_ENTITY_DESTINATION_PORT"; break; case FLOW_SPEC_ENTITY_SOURCE_PORT: return "FLOW_SPEC_ENTITY_SOURCE_PORT"; break; case FLOW_SPEC_ENTITY_ICMP_TYPE: return "FLOW_SPEC_ENTITY_ICMP_TYPE"; break; case FLOW_SPEC_ENTITY_ICMP_CODE: return "FLOW_SPEC_ENTITY_ICMP_CODE"; break; case FLOW_SPEC_ENTITY_TCP_FLAGS: return "FLOW_SPEC_ENTITY_TCP_FLAGS"; break; case FLOW_SPEC_ENTITY_PACKET_LENGTH: return "FLOW_SPEC_ENTITY_PACKET_LENGTH"; break; case FLOW_SPEC_ENTITY_DSCP: return "FLOW_SPEC_ENTITY_DSCP"; break; case FLOW_SPEC_ENTITY_FRAGMENT: return "FLOW_SPEC_ENTITY_FRAGMENT"; break; default: return "UNKNOWN"; break; } } bool flow_spec_decode_nlri_value(uint8_t* data_ptr, uint32_t data_length, flow_spec_rule_t& flow_spec_rule) { // We make copy because we will change this value so often uint8_t* local_data_ptr = data_ptr; uint8_t* packet_end = data_ptr + data_length; /* Flow specification components must follow strict type ordering. * A given component type may or may not be present in the specification, but * if present, * it MUST precede any component of higher numeric type value. * Source: https://tools.ietf.org/html/rfc5575 */ // logger << log4cpp::Priority::WARN << "Hex dump of NLRI value:" ; // logger << log4cpp::Priority::WARN << // print_binary_string_as_hex_with_leading_0x(data_ptr, // data_length); // logger << log4cpp::Priority::WARN ; // We could use zero here because we haven't zero type for BGP Flow Spec // elements uint8_t last_processed_type = 0; while (local_data_ptr < packet_end) { if (*local_data_ptr < last_processed_type) { logger << log4cpp::Priority::WARN << "RFC violation detected. Implementation sent BGP flow spec " "elements in incorrect order"; } last_processed_type = *local_data_ptr; // logger << log4cpp::Priority::WARN << "Process type: " << get_flow_spec_type_name_by_number(last_processed_type); // At this moment if loop is still running then we have at least one byte here and we can safely read it uint8_t current_type = *local_data_ptr; // Decode IPv4 prefixes if (current_type == FLOW_SPEC_ENTITY_SOURCE_PREFIX) { // We've found BGP encoded subnet if (current_type == FLOW_SPEC_ENTITY_SOURCE_PREFIX && flow_spec_rule.source_subnet_ipv4_used) { logger << log4cpp::Priority::WARN << "For some strange reasons we got second source prefix. " "Only one allowed"; return false; } // Calculate maximum length of current field uint32_t field_length = packet_end - local_data_ptr; // We need least two bytes here (type + prefix length) if (field_length < 2) { logger << log4cpp::Priority::WARN << "Too short packet. We need more data for bgp encoded subnet"; return false; } // Skip type field local_data_ptr++; subnet_cidr_mask_t extracted_prefix; uint32_t parsed_nlri_length = 0; // We need to subtract 1 from field_length as we have Flow Spec type included in it bool decode_nlri_result = decode_bgp_subnet_encoding_ipv4(field_length - 1, local_data_ptr, extracted_prefix, parsed_nlri_length); if (!decode_nlri_result) { logger << log4cpp::Priority::WARN << "Could not decode FLOW_SPEC_ENTITY_DESTINATION_PREFIX"; return false; } if (current_type == FLOW_SPEC_ENTITY_SOURCE_PREFIX) { flow_spec_rule.set_source_subnet_ipv4(extracted_prefix); } // Move forward on length of read prefix local_data_ptr += parsed_nlri_length; } else if (current_type == FLOW_SPEC_ENTITY_DESTINATION_PREFIX) { // We've found BGP encoded subnet if (current_type == FLOW_SPEC_ENTITY_DESTINATION_PREFIX && flow_spec_rule.destination_subnet_ipv4_used) { logger << log4cpp::Priority::WARN << "For some strange reasons we got two second destination prefix"; return false; } // Calculate maximum length of current field uint32_t field_length = packet_end - local_data_ptr; // We need least two bytes here (type + prefix length) if (field_length < 2) { logger << log4cpp::Priority::WARN << "Too short packet. We need more data for bgp encoded subnet"; return false; } // Skip type field local_data_ptr++; subnet_cidr_mask_t extracted_prefix; uint32_t parsed_nlri_length = 0; // We need to subtract 1 from field_length as we have Flow Spec type included in it bool decode_nlri_result = decode_bgp_subnet_encoding_ipv4(field_length - 1, local_data_ptr, extracted_prefix, parsed_nlri_length); if (!decode_nlri_result) { logger << log4cpp::Priority::WARN << "Could not decode FLOW_SPEC_ENTITY_DESTINATION_PREFIX"; return false; } if (current_type == FLOW_SPEC_ENTITY_DESTINATION_PREFIX) { flow_spec_rule.set_destination_subnet_ipv4(extracted_prefix); } // Move forward on length of read prefix local_data_ptr += parsed_nlri_length; } else if (current_type == FLOW_SPEC_ENTITY_PORT) { // Different type of port's if (current_type == FLOW_SPEC_ENTITY_PORT) { logger << log4cpp::Priority::WARN << "We do not support common ports"; return false; } } else if (current_type == FLOW_SPEC_ENTITY_IP_PROTOCOL) { // Skip type field local_data_ptr++; uint32_t scanned_bytes = 0; multiple_flow_spec_enumerable_items_t scanned_items; bool result = read_one_or_more_values_encoded_with_operator_byte(local_data_ptr, packet_end, scanned_bytes, scanned_items); if (!result) { logger << log4cpp::Priority::WARN << "read_one_or_more_values_encoded_with_operator_byte returned error but we may have some values parsed before issue happened"; return false; } for (auto extracted_item : scanned_items) { // Do sanity checks for protocol number if (extracted_item.two_byte_value > 255) { logger << log4cpp::Priority::ERROR << "Protocol number value " << extracted_item.two_byte_value << " exceeds maximum 255"; return false; } ip_protocol_t protocol = get_ip_protocol_enum_type_from_integer(uint8_t(extracted_item.two_byte_value)); flow_spec_rule.add_protocol(protocol); } local_data_ptr += scanned_bytes; } else if (current_type == FLOW_SPEC_ENTITY_TCP_FLAGS) { // Skip type field local_data_ptr++; uint32_t scanned_bytes = 0; multiple_flow_spec_enumerable_items_t scanned_items; bool result = read_one_or_more_values_encoded_with_operator_byte(local_data_ptr, packet_end, scanned_bytes, scanned_items); if (!result) { logger << log4cpp::Priority::WARN << "read_one_or_more_values_encoded_with_operator_byte returned error but we may have some values parsed before issue happened"; return false; } for (auto extracted_item : scanned_items) { if (extracted_item.value_length != 1) { logger << log4cpp::Priority::WARN << "We do not support two byte encoded tcp fields"; return false; } // logger << log4cpp::Priority::WARN << "We have " << // extracted_item.value_length << " byte // encoded tcp // option field" ; bgp_flowspec_one_byte_byte_encoded_tcp_flags_t* bgp_flowspec_one_byte_byte_encoded_tcp_flags = (bgp_flowspec_one_byte_byte_encoded_tcp_flags_t*)&extracted_item.one_byte_value; // logger << log4cpp::Priority::WARN << bgp_flowspec_one_byte_byte_encoded_tcp_flags->print(); auto flagset = convert_one_byte_encoding_to_flowset(*bgp_flowspec_one_byte_byte_encoded_tcp_flags); if (flagset.we_have_least_one_flag_enabled()) { flow_spec_rule.add_tcp_flagset(flagset); } } local_data_ptr += scanned_bytes; } else if (current_type == FLOW_SPEC_ENTITY_FRAGMENT) { // Skip type field local_data_ptr++; uint32_t scanned_bytes = 0; multiple_flow_spec_enumerable_items_t scanned_items; bool result = read_one_or_more_values_encoded_with_operator_byte(local_data_ptr, packet_end, scanned_bytes, scanned_items); if (!result) { logger << log4cpp::Priority::WARN << "read_one_or_more_values_encoded_with_operator_byte returned error but we may have some values parsed before issue happened"; return false; } for (auto extracted_item : scanned_items) { if (extracted_item.value_length != 1) { logger << log4cpp::Priority::WARN << "We could not encode fragmentation with two bytes"; return false; } bgp_flow_spec_fragmentation_entity_t* bgp_flow_spec_fragmentation_entity = (bgp_flow_spec_fragmentation_entity_t*)&extracted_item.one_byte_value; // logger << log4cpp::Priority::WARN << "Fragmentation header: " << // bgp_flow_spec_fragmentation_entity->print() ; if (bgp_flow_spec_fragmentation_entity->last_fragment == 1) { flow_spec_rule.add_fragmentation_flag(flow_spec_fragmentation_types_t::FLOW_SPEC_LAST_FRAGMENT); } if (bgp_flow_spec_fragmentation_entity->first_fragment == 1) { flow_spec_rule.add_fragmentation_flag(flow_spec_fragmentation_types_t::FLOW_SPEC_FIRST_FRAGMENT); } if (bgp_flow_spec_fragmentation_entity->is_fragment == 1) { flow_spec_rule.add_fragmentation_flag(flow_spec_fragmentation_types_t::FLOW_SPEC_IS_A_FRAGMENT); } if (bgp_flow_spec_fragmentation_entity->dont_fragment == 1) { flow_spec_rule.add_fragmentation_flag(flow_spec_fragmentation_types_t::FLOW_SPEC_DONT_FRAGMENT); } // What we should do with flag FLOW_SPEC_NOT_A_FRAGMENT from bgp_flow_spec // class? // Very interesting because we haven't something like this in protocol // :) // In ExaBGP Thomas Mangin interpret this case as "not a fragment" // So when we haven't any other flags enabled we interpret it as "not // a // fragment" if (bgp_flow_spec_fragmentation_entity->last_fragment == 0 && bgp_flow_spec_fragmentation_entity->first_fragment == 0 && bgp_flow_spec_fragmentation_entity->is_fragment == 0 && bgp_flow_spec_fragmentation_entity->dont_fragment == 0) { flow_spec_rule.add_fragmentation_flag(flow_spec_fragmentation_types_t::FLOW_SPEC_NOT_A_FRAGMENT); } } local_data_ptr += scanned_bytes; } else if (current_type == FLOW_SPEC_ENTITY_PACKET_LENGTH) { // Skip type field local_data_ptr++; uint32_t scanned_bytes = 0; multiple_flow_spec_enumerable_items_t scanned_items; bool result = read_one_or_more_values_encoded_with_operator_byte(local_data_ptr, packet_end, scanned_bytes, scanned_items); if (!result) { logger << log4cpp::Priority::WARN << "read_one_or_more_values_encoded_with_operator_byte returned error but we may have some values parsed before issue happened"; return false; } for (auto extracted_item : scanned_items) { flow_spec_rule.add_packet_length(extracted_item.two_byte_value); } local_data_ptr += scanned_bytes; } else if (current_type == FLOW_SPEC_ENTITY_SOURCE_PORT) { // Skip type field local_data_ptr++; uint32_t scanned_bytes = 0; multiple_flow_spec_enumerable_items_t scanned_items; bool result = read_one_or_more_values_encoded_with_operator_byte(local_data_ptr, packet_end, scanned_bytes, scanned_items); if (!result) { logger << log4cpp::Priority::WARN << "read_one_or_more_values_encoded_with_operator_byte returned error but we may have some values parsed before issue happened"; return false; } for (auto extracted_item : scanned_items) { flow_spec_rule.add_source_port(extracted_item.two_byte_value); } local_data_ptr += scanned_bytes; } else if (current_type == FLOW_SPEC_ENTITY_DESTINATION_PORT) { // Skip type field local_data_ptr++; uint32_t scanned_bytes = 0; multiple_flow_spec_enumerable_items_t scanned_items; bool result = read_one_or_more_values_encoded_with_operator_byte(local_data_ptr, packet_end, scanned_bytes, scanned_items); if (!result) { logger << log4cpp::Priority::WARN << "read_one_or_more_values_encoded_with_operator_byte returned error but we may have some values parsed before issue happened"; return false; } for (auto extracted_item : scanned_items) { flow_spec_rule.add_destination_port(extracted_item.two_byte_value); } local_data_ptr += scanned_bytes; } else { logger << log4cpp::Priority::WARN << "We could not handle flow spec element type " << uint32_t(*local_data_ptr) << " pretty type: " << get_flow_spec_type_name_by_number(*local_data_ptr); return false; } } if (local_data_ptr != packet_end) { logger << log4cpp::Priority::WARN << "For some strange reasons we did not parse whole packet"; } return true; } bool read_one_or_more_values_encoded_with_operator_byte(uint8_t* start, uint8_t* packet_end, uint32_t& readed_bytes, multiple_flow_spec_enumerable_items_t& multiple_flow_spec_enumerable_items) { // TODO: pretty danegrous idea to do infinite loop and we are using 100 // iterations here for // worst case uint8_t* local_data_ptr = start; for (int i = 0; i < 100; i++) { if (packet_end - local_data_ptr < sizeof(bgp_flow_spec_operator_byte_t)) { logger << log4cpp::Priority::WARN << "Too short data for FLOW_SPEC_ENTITY_IP_PROTOCOL"; return false; } bgp_flow_spec_operator_byte_t* bgp_flow_spec_operator_byte = (bgp_flow_spec_operator_byte_t*)local_data_ptr; // logger << log4cpp::Priority::WARN << "Byte operator: " << // bgp_flow_spec_operator_byte->print() << // std::endl; // We do not support almost all custom fields if (bgp_flow_spec_operator_byte->less_than == 1 or bgp_flow_spec_operator_byte->greater_than == 1) { logger << log4cpp::Priority::WARN << "We do not support greater than or lower than in flow spec"; return false; } if (bgp_flow_spec_operator_byte->and_bit == 1) { logger << log4cpp::Priority::WARN << "We do not support and opertations in flow spec"; return false; } if (bgp_flow_spec_operator_byte->get_value_length() != 2 && bgp_flow_spec_operator_byte->get_value_length() != 1) { logger << log4cpp::Priority::WARN << "We could encode data only with 1 or 2 bytes. "; return false; } if (packet_end - local_data_ptr < sizeof(bgp_flow_spec_operator_byte_t) + bgp_flow_spec_operator_byte->get_value_length()) { logger << log4cpp::Priority::WARN << "Not enough data for bgp_flow_spec_operator_byte_t"; return false; } flow_spec_enumerable_lement element; if (bgp_flow_spec_operator_byte->get_value_length() == 1) { element.one_byte_value = *((uint8_t*)(local_data_ptr + sizeof(bgp_flow_spec_operator_byte_t))); // We will sue two byte version as common accessor element.two_byte_value = element.one_byte_value; element.value_length = 1; element.operator_byte = *bgp_flow_spec_operator_byte; multiple_flow_spec_enumerable_items.push_back(element); } else if (bgp_flow_spec_operator_byte->get_value_length() == 2) { element.two_byte_value = *((uint16_t*)(local_data_ptr + sizeof(bgp_flow_spec_operator_byte_t))); // TODO: not sure about it? We really need it? element.two_byte_value = ntohs(element.two_byte_value); element.operator_byte = *bgp_flow_spec_operator_byte; multiple_flow_spec_enumerable_items.push_back(element); } else { logger << log4cpp::Priority::WARN << "Unexpected length for flow spec enumerable value: " << uint32_t(bgp_flow_spec_operator_byte->get_value_length()); return false; } // Shift pointer to next element local_data_ptr += sizeof(bgp_flow_spec_operator_byte_t) + bgp_flow_spec_operator_byte->get_value_length(); // If this was last lement in list just stop this loop if (bgp_flow_spec_operator_byte->end_of_list == 1) { break; } } // Return number of scanned bytes readed_bytes = local_data_ptr - start; return 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.agent_addresses != rhs.agent_addresses) { return false; } if (lhs.source_asns != rhs.source_asns) { return false; } if (lhs.destination_asns != rhs.destination_asns) { return false; } if (lhs.input_interfaces != rhs.input_interfaces) { return false; } if (lhs.output_interfaces != rhs.output_interfaces) { 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); } } if (json_doc.contains("source_asns")) { std::vector asns_as_ints; // JSON library will allow negative values even if we ask uint32_t as type // That's why we use int64_t and then implement range checking try { asns_as_ints = json_doc["source_asns"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode source_asns " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode source_asns"; return false; } for (auto asn : asns_as_ints) { if (asn < 0) { logger << log4cpp::Priority::ERROR << "Could not parse ASN, it cannot be negative: " << asn; return false; } if (asn > std::numeric_limits::max()) { logger << log4cpp::Priority::ERROR << "Could not parse ASN, it cannot be bigger then: " << std::numeric_limits::max(); return false; } flow_spec_rule.add_source_asn((uint32_t)asn); } } if (json_doc.contains("destination_asns")) { std::vector asns_as_ints; // JSON library will allow negative values even if we ask uint32_t as type // That's why we use int64_t and then implement range checking try { asns_as_ints = json_doc["destination_asns"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode destination_asns " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode destination_asns"; return false; } for (auto asn : asns_as_ints) { if (asn < 0) { logger << log4cpp::Priority::ERROR << "Could not parse ASN, it cannot be negative: " << asn; return false; } if (asn > std::numeric_limits::max()) { logger << log4cpp::Priority::ERROR << "Could not parse ASN, it cannot be bigger then: " << std::numeric_limits::max(); return false; } flow_spec_rule.add_destination_asn(asn); } } if (json_doc.contains("input_interfaces")) { std::vector interfaces_as_ints; // JSON library will allow negative values even if we ask uint32_t as type // That's why we use int64_t and then implement range checking try { interfaces_as_ints = json_doc["input_interfaces"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode source_asns " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode intput_interfaces"; return false; } for (auto interface : interfaces_as_ints) { if (interface < 0) { logger << log4cpp::Priority::ERROR << "Could not parse interface, it cannot be negative: " << interface; return false; } if (interface > std::numeric_limits::max()) { logger << log4cpp::Priority::ERROR << "Could not parse interface, it cannot be bigger then: " << std::numeric_limits::max(); return false; } flow_spec_rule.add_input_interface((uint32_t)interface); } } if (json_doc.contains("output_interfaces")) { std::vector interfaces_as_ints; // JSON library will allow negative values even if we ask uint32_t as type // That's why we use int64_t and then implement range checking try { interfaces_as_ints = json_doc["output_interfaces"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode source_asns " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode intput_interfaces"; return false; } for (auto interface : interfaces_as_ints) { if (interface < 0) { logger << log4cpp::Priority::ERROR << "Could not parse interface, it cannot be negative: " << interface; return false; } if (interface > std::numeric_limits::max()) { logger << log4cpp::Priority::ERROR << "Could not parse interface, it cannot be bigger then: " << std::numeric_limits::max(); return false; } flow_spec_rule.add_output_interface((uint32_t)interface); } } // 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); } } 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("agent_addresses")) { std::vector agent_addresses_vector_as_strings; try { agent_addresses_vector_as_strings = json_doc["agent_addresses"].get>(); } catch (nlohmann::json::exception& e) { logger << log4cpp::Priority::ERROR << "Could not decode agent_addresses " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not decode agent_addresses"; return false; } for (const auto& agent_address_as_string : agent_addresses_vector_as_strings) { uint32_t ipv4_agent_address = 0; auto ip_parser_result = convert_ip_as_string_to_uint_safe(agent_address_as_string, ipv4_agent_address); if (!ip_parser_result) { logger << log4cpp::Priority::ERROR << "Could not parse this " << agent_address_as_string << " as IPv4 address"; return false; } flow_spec_rule.add_agent_address(ipv4_agent_address); } } 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.source_asns.empty()) { flow_json["source_asns"] = flow_spec_rule.source_asns; } if (!flow_spec_rule.destination_asns.empty()) { flow_json["destination_asns"] = flow_spec_rule.destination_asns; } if (!flow_spec_rule.input_interfaces.empty()) { flow_json["input_interfaces"] = flow_spec_rule.input_interfaces; } if (!flow_spec_rule.output_interfaces.empty()) { flow_json["output_interfaces"] = flow_spec_rule.output_interfaces; } 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.ipv4_nexthops.empty()) { flow_json["ipv4_nexthops"] = nlohmann::json::array(); for (auto ipv4_next_hop : flow_spec_rule.ipv4_nexthops) { flow_json["ipv4_nexthops"].push_back(convert_ip_as_uint_to_string(ipv4_next_hop)); } } if (!flow_spec_rule.agent_addresses.empty()) { flow_json["agent_addresses"] = nlohmann::json::array(); for (auto agent_address_ipv4 : flow_spec_rule.agent_addresses) { flow_json["agent_addresses"].push_back(convert_ip_as_uint_to_string(agent_address_ipv4)); } } 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; } // This function checks that source or destination fields of flow spec rule belong to specified patricia tree // As side effect function returns IP address of our host related to flow spec rule // TODO: this function does not support IPv6 at all bool validate_flow_spec_to_belong_to_patricia(const flow_spec_rule_t& flow_spec_rule, const lookup_tree_32bit_t& lookup_tree_ipv4, const lookup_tree_128bit_t& lookup_tree_ipv6, uint32_t& client_ip) { if (!(flow_spec_rule.source_subnet_ipv4_used || flow_spec_rule.destination_subnet_ipv4_used || flow_spec_rule.source_subnet_ipv6_used || flow_spec_rule.destination_subnet_ipv6_used)) { logger << log4cpp::Priority::ERROR << "Both source and destination fields for for both IPv4 and flow spec are empty"; return false; } // Check prefix lengths for source prefix if (flow_spec_rule.source_subnet_ipv4_used && flow_spec_rule.source_subnet_ipv4.cidr_prefix_length != 32) { logger << log4cpp::Priority::ERROR << "We allow only /32 announces for destination IPv4 prefixes"; return false; } if (flow_spec_rule.source_subnet_ipv6_used && flow_spec_rule.source_subnet_ipv6.cidr_prefix_length != 128) { logger << log4cpp::Priority::ERROR << "We allow only /128 announces for destination IPv6 prefixes"; return false; } // Check prefix lengths for destination prefix if (flow_spec_rule.destination_subnet_ipv4_used && flow_spec_rule.destination_subnet_ipv4.cidr_prefix_length != 32) { logger << log4cpp::Priority::ERROR << "We allow only /32 announces for destination IPv4 prefixes"; return false; } if (flow_spec_rule.destination_subnet_ipv6_used && flow_spec_rule.destination_subnet_ipv6.cidr_prefix_length != 128) { logger << log4cpp::Priority::ERROR << "We allow only /128 announces for destination IPv6 prefixes"; return false; } // TODO: we do not have Patricia lookup logic in place and we just disable it if (flow_spec_rule.destination_subnet_ipv6_used || flow_spec_rule.source_subnet_ipv6_used) { logger << log4cpp::Priority::ERROR << "Validation in IPv6 mode is not supported yet"; return false; } if (flow_spec_rule.destination_subnet_ipv4_used && flow_spec_rule.source_subnet_ipv4_used) { // We have both networks specified // Lookup destination network bool we_found_destination_subnet = lookup_tree_ipv4.lookup_network(flow_spec_rule.destination_subnet_ipv4); // Lookup source network bool we_found_source_subnet = lookup_tree_ipv4.lookup_network(flow_spec_rule.source_subnet_ipv4); if (!we_found_destination_subnet && !we_found_source_subnet) { logger << log4cpp::Priority::ERROR << "Both source and destination addresses do not belong to your ranges"; return false; } if (we_found_destination_subnet) { client_ip = flow_spec_rule.destination_subnet_ipv4.subnet_address; } if (we_found_source_subnet) { client_ip = flow_spec_rule.source_subnet_ipv4.subnet_address; } return true; } else if (flow_spec_rule.destination_subnet_ipv4_used) { // We have only destination network bool we_found_this_subnet = lookup_tree_ipv4.lookup_network(flow_spec_rule.destination_subnet_ipv4); if (!we_found_this_subnet) { logger << log4cpp::Priority::ERROR << "Could not find destination subnet in our networks list"; return false; } client_ip = flow_spec_rule.destination_subnet_ipv4.subnet_address; return true; } else if (flow_spec_rule.source_subnet_ipv4_used) { // We have only source network bool we_found_this_subnet = lookup_tree_ipv4.lookup_network(flow_spec_rule.source_subnet_ipv4); if (!we_found_this_subnet) { logger << log4cpp::Priority::ERROR << "Could not find source subnet in our networks list"; return false; } client_ip = flow_spec_rule.source_subnet_ipv4.subnet_address; return true; } return true; } // This function checks that source or destination fields of flow spec rule belong to specified IP address bool validate_flow_spec_ipv4(const flow_spec_rule_t& flow_spec_rule, uint32_t client_ip_as_integer) { if (!(flow_spec_rule.source_subnet_ipv4_used || flow_spec_rule.destination_subnet_ipv4_used)) { logger << log4cpp::Priority::ERROR << "both source and destination fields for flow spec are empty"; return false; } // // Prevent packets which exceeds 1500 (default MTU) for (auto packet_length : flow_spec_rule.packet_lengths) { if (packet_length > reject_flow_spec_validation_if_slow_spec_length_exceeds_this_number) { logger << log4cpp::Priority::ERROR << "Flow spec's length field " << packet_length << " exceeds maximum allowed value " << reject_flow_spec_validation_if_slow_spec_length_exceeds_this_number; return false; } } // At this step we have least one (src or dst) field // TODO: we could not check src/dst fields wider than /32 at this moment! Add this feature! subnet_cidr_mask_t client_subnet(client_ip_as_integer, 32); if (flow_spec_rule.source_subnet_ipv4_used && client_subnet == flow_spec_rule.source_subnet_ipv4) { return true; } if (flow_spec_rule.destination_subnet_ipv4_used && client_subnet == flow_spec_rule.destination_subnet_ipv4) { return true; } logger << log4cpp::Priority::ERROR << "flow spec validation failed because src or dst subnets in flow spec does not match customer IP"; return false; } // Is it range valid for port? bool valid_port(int32_t port) { return port >= 0 && port <= 65535; } pavel-odintsov-fastnetmon-394fbe0/src/bgp_protocol_flow_spec.hpp000066400000000000000000000674161520703010000253120ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "dynamic_binary_buffer.hpp" #include "fast_library.hpp" #include "fastnetmon_networks.hpp" #include #include "iana/iana_ip_protocols.hpp" #include "bgp_protocol.hpp" #include "ip_lookup_tree.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 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_source_asn(uint32_t source_asn) { this->source_asns.push_back(source_asn); } void add_destination_asn(uint32_t destination_asn) { this->destination_asns.push_back(destination_asn); } void add_agent_address(uint32_t agent_ip) { this->agent_addresses.push_back(agent_ip); } void add_input_interface(uint32_t interface) { this->input_interfaces.push_back(interface); } void add_output_interface(uint32_t interface) { this->output_interfaces.push_back(interface); } 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(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(source_asns); ar& BOOST_SERIALIZATION_NVP(destination_asns); ar& BOOST_SERIALIZATION_NVP(agent_addresses); ar& BOOST_SERIALIZATION_NVP(input_interfaces); ar& BOOST_SERIALIZATION_NVP(output_interfaces); 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 IPv4 addresses std::vector agent_addresses; 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; // This one is an non standard extension for our own purposes std::vector source_asns; std::vector destination_asns; // This one is an non standard extension for our own purposes std::vector input_interfaces; std::vector output_interfaces; // 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"); std::vector build_attributes_for_flowspec_announce(flow_spec_rule_t flow_spec_rule); bool encode_bgp_flow_spec_elements_into_bgp_mp_attribute(const flow_spec_rule_t& flow_spec_rule, dynamic_binary_buffer_t& bgp_mp_ext_flow_spec_header_as_binary_array, bool add_preamble); 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); bgp_flowspec_one_byte_byte_encoded_tcp_flags_t return_in_one_byte_encoding(const flow_spec_tcp_flagset_t& flagset); flow_spec_tcp_flagset_t convert_one_byte_encoding_to_flowset(const bgp_flowspec_one_byte_byte_encoded_tcp_flags_t& ony_byte_flags); void uint8t_representation_of_tcp_flags_to_flow_spec(uint8_t tcp_flags, flow_spec_tcp_flagset_t& flagset); bool validate_flow_spec_ipv4(const flow_spec_rule_t& flow_spec_rule, uint32_t client_ip_as_integer); bool valid_port(int32_t port); bool validate_flow_spec_to_belong_to_patricia(const flow_spec_rule_t& flow_spec_rule, const lookup_tree_32bit_t& lookup_tree_ipv4, const lookup_tree_128bit_t& lookup_tree_ipv6, uint32_t& client_ip); bool encode_bgp_flow_spec_next_hop_as_extended_attribute(uint32_t next_hop_ipv4, dynamic_binary_buffer_t& extended_attributes_as_binary_array); 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); pavel-odintsov-fastnetmon-394fbe0/src/conanfile.txt000066400000000000000000000005371520703010000225350ustar00rootroot00000000000000[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 pavel-odintsov-fastnetmon-394fbe0/src/dynamic_binary_buffer.hpp000066400000000000000000000120211520703010000250570ustar00rootroot00000000000000#pragma once #include class dynamic_binary_buffer_t { public: dynamic_binary_buffer_t() : byte_storage(nullptr), internal_storage_size(0) { // std::cout << "Default constructor called" << std::endl; } dynamic_binary_buffer_t(dynamic_binary_buffer_t&& source) noexcept { // Just copy all field values from source and zeroify it this->internal_data_shift = source.internal_data_shift; source.internal_data_shift = 0; this->byte_storage = source.byte_storage; source.byte_storage = nullptr; this->internal_storage_size = source.internal_storage_size; source.internal_storage_size = 0; this->errors_occured = source.errors_occured; source.errors_occured = false; } // We should set buffer size here. bool set_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) { 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; 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->internal_storage_size = that.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->internal_storage_size > 0) { // Allocate memory for new instance this->set_buffer_size_in_bytes(this->internal_storage_size); memcpy(this->byte_storage, that.byte_storage, that.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 to ensure that we have enough space for one more byte if (internal_data_shift + sizeof(uint8_t) > internal_storage_size) { 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 if (internal_data_shift + dynamic_binary_buffer.get_used_size() > internal_storage_size) { 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 > internal_storage_size) { 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) > internal_storage_size) { errors_occured = true; return false; } memcpy(byte_storage + internal_data_shift, ptr, sizeof(src_type)); internal_data_shift += sizeof(src_type); return true; } // Return full size (with non initialized data region too) uint32_t get_full_size() const { return internal_storage_size; } // Return only used memory region size_t get_used_size() const { return internal_data_shift; } const uint8_t* get_pointer() const { return byte_storage; } // If we have any issues with it bool is_failed() const { return errors_occured; } private: size_t internal_data_shift = 0; uint8_t* byte_storage = nullptr; ssize_t internal_storage_size = 0; // If any errors occurred in any time when we used this buffer bool errors_occured = false; }; static_assert(std::is_move_constructible_v); static_assert(std::is_nothrow_move_constructible_v); pavel-odintsov-fastnetmon-394fbe0/src/example_plugin/000077500000000000000000000000001520703010000230425ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/example_plugin/example_collector.cpp000066400000000000000000000035171520703010000272550ustar00rootroot00000000000000#include "../all_logcpp_libraries.hpp" // For config map operations #include #include #include "../iana/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); } pavel-odintsov-fastnetmon-394fbe0/src/example_plugin/example_collector.hpp000066400000000000000000000003201520703010000272470ustar00rootroot00000000000000#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 pavel-odintsov-fastnetmon-394fbe0/src/fast_endianless.hpp000066400000000000000000000034421520703010000237070ustar00rootroot00000000000000#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; pavel-odintsov-fastnetmon-394fbe0/src/fast_library.cpp000066400000000000000000002534101520703010000232230ustar00rootroot00000000000000#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/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_ipv4_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_context io_context; boost::asio::ip::tcp::resolver resolver(io_context); auto results = resolver.resolve(domain_name, ""); for (const auto& entry : results) { return entry.endpoint().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 // Crafts capnp packet void craft_capnp_for_simple_packet(const simple_packet_t& packet, SimplePacketType::Builder& capnp_packet) { 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.setIpLength(packet.ip_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_ipv4_address); if (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)); } // Add MAC addresses kj::ArrayPtr source_mac_as_kj_array((kj::byte*)&packet.source_mac, sizeof(packet.source_mac)); capnp_packet.setSrcMac(capnp::Data::Reader(source_mac_as_kj_array)); kj::ArrayPtr destination_mac_as_kj_array((kj::byte*)&packet.destination_mac, sizeof(packet.destination_mac)); capnp_packet.setDstMac(capnp::Data::Reader(destination_mac_as_kj_array)); } 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_ipv4_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; } bool write_simple_packet(int fd, bool write_message_length, const simple_packet_t& packet, int send_flags) { extern log4cpp::Category& logger; ::capnp::MallocMessageBuilder message; auto capnp_packet = message.initRoot(); // Craft Capnp message craft_capnp_for_simple_packet(packet, capnp_packet); kj::Array words; // For some unknown reasons function writePackedMessageToFd sends incorrect, too short data and we use regular send for better flexibility try { words = messageToFlatArray(message); } catch (...) { logger << log4cpp::Priority::ERROR << "messageToFlatArray failed with error"; return false; } kj::ArrayPtr bytes = words.asBytes(); size_t message_length = bytes.size(); if (write_message_length) { // To avoid dependency on platform specific type size_t we use uint64_t instead // https://en.cppreference.com/w/c/types/size_t uint64_t portable_message_length = message_length; ssize_t message_length_write_result = send(fd, &portable_message_length, sizeof(portable_message_length), send_flags); // If write returned error then stop processing if (message_length_write_result < 0) { // If we received error from it, let's provide details about it in DEBUG mode if (message_length_write_result == -1) { logger << log4cpp::Priority::DEBUG << "write in write_simple_packet for message length returned error: " << errno << " " << strerror(errno); } return false; } // we could not write whole packet notify caller about it if (message_length_write_result != sizeof(portable_message_length)) { logger << log4cpp::Priority::DEBUG << "write in write_simple_packet for message length did not write all data"; return false; } } ssize_t write_result = send(fd, bytes.begin(), message_length, send_flags); // 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 << " " << strerror(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; } return true; } // Encode simple packet into special capnp structure for serialization bool write_simple_packet_to_tls_socket(SSL* tls_fd, const simple_packet_t& packet) { extern log4cpp::Category& logger; ::capnp::MallocMessageBuilder message; auto capnp_packet = message.initRoot(); // Craft Capnp message craft_capnp_for_simple_packet(packet, capnp_packet); kj::Array words; // For some unknown reasons function writePackedMessageToFd sends incorrect, too short data and we use regular send for better flexibility try { words = messageToFlatArray(message); } catch (...) { logger << log4cpp::Priority::ERROR << "messageToFlatArray failed with error"; return false; } kj::ArrayPtr bytes = words.asBytes(); size_t message_length = bytes.size(); // To avoid dependency on platform specific type size_t we use uint64_t instead // https://en.cppreference.com/w/c/types/size_t uint64_t portable_message_length = message_length; // TODO: unfortunately, it can fire SIGPIPE signal and there are no documented way to stop it :( // It's clearly topic to raise with OpenSSL team // https://github.com/openssl/openssl/issues/16399 int message_length_write_result = SSL_write(tls_fd, &portable_message_length, sizeof(portable_message_length)); // If write returned error then stop processing if (message_length_write_result <= 0) { // unsigned long error_code = ERR_get_error(); // We had some issue with this logging function as it cropped output this way: // "TLS write for header failed with error:" // logger << log4cpp::Priority::ERROR << "TLS write for header failed with error: " << ERR_reason_error_string(error_code) << " error code " << error_code; return false; } // we could not write whole packet notify caller about it if (message_length_write_result != sizeof(portable_message_length)) { logger << log4cpp::Priority::DEBUG << "write in write_simple_packet for message length did not write all data"; return false; } // TODO: unfortunately, it can fire SIGPIPE signal and there are no documented way to stop it :( // It's clearly topic to raise with OpenSSL team // https://github.com/openssl/openssl/issues/16399 int write_result = SSL_write(tls_fd, bytes.begin(), message_length); // If write returned error then stop processing if (write_result <= 0) { // unsigned long error_code = ERR_get_error(); // logger << log4cpp::Priority::ERROR << "TLS write for message body failed with error: " << ERR_reason_error_string(error_code) << " error code " << error_code; 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; } 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::make_address(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(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_context ios; boost::asio::ip::tcp::resolver r(ios); boost::asio::ip::tcp::socket sock(ios); auto end_point = r.resolve(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; } pavel-odintsov-fastnetmon-394fbe0/src/fast_library.hpp000066400000000000000000000221111520703010000232200ustar00rootroot00000000000000#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 #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); #ifdef ENABLE_CAPNP bool write_simple_packet_to_tls_socket(SSL* tls_fd, const simple_packet_t& packet); #endif pavel-odintsov-fastnetmon-394fbe0/src/fast_platform.h.template000066400000000000000000000021651520703010000246610ustar00rootroot00000000000000#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}"; }; pavel-odintsov-fastnetmon-394fbe0/src/fast_priority_queue.cpp000066400000000000000000000044561520703010000246500ustar00rootroot00000000000000bool 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; } } pavel-odintsov-fastnetmon-394fbe0/src/fast_priority_queue.hpp000066400000000000000000000014421520703010000246450ustar00rootroot00000000000000#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 pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon.conf000066400000000000000000000262471520703010000230710ustar00rootroot00000000000000### ### 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 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 pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon.cpp000066400000000000000000002261651520703010000227270ustar00rootroot00000000000000/* 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); } pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon.service.in000066400000000000000000000007341520703010000242020ustar00rootroot00000000000000[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 pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_actions.hpp000066400000000000000000000003461520703010000244430ustar00rootroot00000000000000#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; pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_api_client.cpp000066400000000000000000000101611520703010000251010ustar00rootroot00000000000000#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; } pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_client.cpp000066400000000000000000000055221520703010000242550ustar00rootroot00000000000000#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(); } pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_configuration_scheme.hpp000066400000000000000000000053761520703010000272060ustar00rootroot00000000000000#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 }; bool mirror_af_packet_sampling_rate = 1; // 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" }; }; pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_internal_api.proto000066400000000000000000000125321520703010000260240ustar00rootroot00000000000000syntax = "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; } pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_logic.cpp000066400000000000000000004312161520703010000240770ustar00rootroot00000000000000#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 #ifdef USING_MONGO2 #include #include #else #include #include #endif #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; } } pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_logic.hpp000066400000000000000000000140121520703010000240730ustar00rootroot00000000000000#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); pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_logrotate000066400000000000000000000002021520703010000242040ustar00rootroot00000000000000/var/log/fastnetmon.log { rotate 31 daily size 10M missingok copytruncate notifempty delaycompress } pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_networks.hpp000066400000000000000000000135431520703010000246620ustar00rootroot00000000000000#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; }; pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_pcap_format.cpp000066400000000000000000000055471520703010000253010ustar00rootroot00000000000000#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; } pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_pcap_format.hpp000066400000000000000000000130301520703010000252700ustar00rootroot00000000000000#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; }; pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_plugin.hpp000066400000000000000000000006421520703010000243000ustar00rootroot00000000000000#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; pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_simple_packet.hpp000066400000000000000000000076401520703010000256270ustar00rootroot00000000000000#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; // TCP numbers uint32_t tcp_sequence_number = 0; uint32_t tcp_ack_number = 0; uint16_t tcp_window_size = 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; // ICMP type uint8_t icmp_type = 0; // ICMP code uint8_t icmp_code = 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; // Pointer to L4 (e.g. UDP/TCP) payload data, past the L4 header const void* l4_payload_pointer = nullptr; // Length of L4 payload uint32_t l4_payload_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; // IPv4 address of device which send this flow uint32_t agent_ipv4_address = 0; // IPv6 address of device which send this flow in6_addr agent_ipv6_address{}; }; pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_tests.cpp000066400000000000000000000076761520703010000241550ustar00rootroot00000000000000#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"); } pavel-odintsov-fastnetmon-394fbe0/src/fastnetmon_types.hpp000066400000000000000000000551011520703010000241460ustar00rootroot00000000000000#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 pavel-odintsov-fastnetmon-394fbe0/src/filter.cpp000066400000000000000000000225161520703010000220300ustar00rootroot00000000000000#include "filter.hpp" #include "iana/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; } pavel-odintsov-fastnetmon-394fbe0/src/filter.hpp000066400000000000000000000005751520703010000220360ustar00rootroot00000000000000#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); pavel-odintsov-fastnetmon-394fbe0/src/fixed_size_packet_storage.hpp000066400000000000000000000026441520703010000257540ustar00rootroot00000000000000#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] = {}; }; pavel-odintsov-fastnetmon-394fbe0/src/fmt/000077500000000000000000000000001520703010000206175ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/fmt/base.h000066400000000000000000003151071520703010000217110ustar00rootroot00000000000000// Formatting library for C++ - the base API for char/UTF-8 // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_BASE_H_ #define FMT_BASE_H_ #if defined(FMT_IMPORT_STD) && !defined(FMT_MODULE) # define FMT_MODULE #endif #ifndef FMT_MODULE # include // CHAR_BIT # include // FILE # include // memcmp # include // std::enable_if #endif // The fmt library version in the form major * 10000 + minor * 100 + patch. #define FMT_VERSION 120100 // Detect compiler versions. #if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) #else # define FMT_CLANG_VERSION 0 #endif #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) # define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) #else # define FMT_GCC_VERSION 0 #endif #if defined(__ICL) # define FMT_ICC_VERSION __ICL #elif defined(__INTEL_COMPILER) # define FMT_ICC_VERSION __INTEL_COMPILER #else # define FMT_ICC_VERSION 0 #endif #if defined(_MSC_VER) # define FMT_MSC_VERSION _MSC_VER #else # define FMT_MSC_VERSION 0 #endif // Detect standard library versions. #ifdef _GLIBCXX_RELEASE # define FMT_GLIBCXX_RELEASE _GLIBCXX_RELEASE #else # define FMT_GLIBCXX_RELEASE 0 #endif #ifdef _LIBCPP_VERSION # define FMT_LIBCPP_VERSION _LIBCPP_VERSION #else # define FMT_LIBCPP_VERSION 0 #endif #ifdef _MSVC_LANG # define FMT_CPLUSPLUS _MSVC_LANG #else # define FMT_CPLUSPLUS __cplusplus #endif // Detect __has_*. #ifdef __has_feature # define FMT_HAS_FEATURE(x) __has_feature(x) #else # define FMT_HAS_FEATURE(x) 0 #endif #ifdef __has_include # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 #endif #ifdef __has_builtin # define FMT_HAS_BUILTIN(x) __has_builtin(x) #else # define FMT_HAS_BUILTIN(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) \ (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) #define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) // Detect C++14 relaxed constexpr. #ifdef FMT_USE_CONSTEXPR // Use the provided definition. #elif FMT_GCC_VERSION >= 702 && FMT_CPLUSPLUS >= 201402L // GCC only allows constexpr member functions in non-literal types since 7.2: // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66297. # define FMT_USE_CONSTEXPR 1 #elif FMT_ICC_VERSION # define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628 #elif FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 # define FMT_USE_CONSTEXPR 1 #else # define FMT_USE_CONSTEXPR 0 #endif #if FMT_USE_CONSTEXPR # define FMT_CONSTEXPR constexpr #else # define FMT_CONSTEXPR #endif // Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated. #ifdef FMT_USE_CONSTEVAL // Use the provided definition. #elif !defined(__cpp_lib_is_constant_evaluated) # define FMT_USE_CONSTEVAL 0 #elif FMT_CPLUSPLUS < 201709L # define FMT_USE_CONSTEVAL 0 #elif FMT_GLIBCXX_RELEASE && FMT_GLIBCXX_RELEASE < 10 # define FMT_USE_CONSTEVAL 0 #elif FMT_LIBCPP_VERSION && FMT_LIBCPP_VERSION < 10000 # define FMT_USE_CONSTEVAL 0 #elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L # define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang < 14. #elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929 # define FMT_USE_CONSTEVAL 0 // consteval is broken in MSVC VS2019 < 16.10. #elif defined(__cpp_consteval) # define FMT_USE_CONSTEVAL 1 #elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101 # define FMT_USE_CONSTEVAL 1 #else # define FMT_USE_CONSTEVAL 0 #endif #if FMT_USE_CONSTEVAL # define FMT_CONSTEVAL consteval # define FMT_CONSTEXPR20 constexpr #else # define FMT_CONSTEVAL # define FMT_CONSTEXPR20 #endif // Check if exceptions are disabled. #ifdef FMT_USE_EXCEPTIONS // Use the provided definition. #elif defined(__GNUC__) && !defined(__EXCEPTIONS) # define FMT_USE_EXCEPTIONS 0 #elif defined(__clang__) && !defined(__cpp_exceptions) # define FMT_USE_EXCEPTIONS 0 #elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS # define FMT_USE_EXCEPTIONS 0 #else # define FMT_USE_EXCEPTIONS 1 #endif #if FMT_USE_EXCEPTIONS # define FMT_TRY try # define FMT_CATCH(x) catch (x) #else # define FMT_TRY if (true) # define FMT_CATCH(x) if (false) #endif #ifdef FMT_NO_UNIQUE_ADDRESS // Use the provided definition. #elif FMT_CPLUSPLUS < 202002L // Not supported. #elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address) # define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] // VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). #elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION # define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] #endif #ifndef FMT_NO_UNIQUE_ADDRESS # define FMT_NO_UNIQUE_ADDRESS #endif #if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) # define FMT_FALLTHROUGH [[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 // Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. #if FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && !defined(__NVCC__) # define FMT_NORETURN [[noreturn]] #else # define FMT_NORETURN #endif #ifdef FMT_NODISCARD // Use the provided definition. #elif FMT_HAS_CPP17_ATTRIBUTE(nodiscard) # define FMT_NODISCARD [[nodiscard]] #else # define FMT_NODISCARD #endif #if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_VISIBILITY(value) __attribute__((visibility(value))) #else # define FMT_VISIBILITY(value) #endif // Detect pragmas. #define FMT_PRAGMA_IMPL(x) _Pragma(#x) #if FMT_GCC_VERSION >= 504 && !defined(__NVCOMPILER) // Workaround a _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884 // and an nvhpc warning: https://github.com/fmtlib/fmt/pull/2582. # define FMT_PRAGMA_GCC(x) FMT_PRAGMA_IMPL(GCC x) #else # define FMT_PRAGMA_GCC(x) #endif #if FMT_CLANG_VERSION # define FMT_PRAGMA_CLANG(x) FMT_PRAGMA_IMPL(clang x) #else # define FMT_PRAGMA_CLANG(x) #endif #if FMT_MSC_VERSION # define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) #else # define FMT_MSC_WARNING(...) #endif // Enable minimal optimizations for more compact code in debug mode. FMT_PRAGMA_GCC(push_options) #if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE) FMT_PRAGMA_GCC(optimize("Og")) # define FMT_GCC_OPTIMIZED #endif FMT_PRAGMA_CLANG(diagnostic push) FMT_PRAGMA_GCC(diagnostic push) #ifdef FMT_ALWAYS_INLINE // Use the provided definition. #elif FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) #else # define FMT_ALWAYS_INLINE inline #endif // A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. #if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED) # define FMT_INLINE FMT_ALWAYS_INLINE #else # define FMT_INLINE inline #endif #ifndef FMT_BEGIN_NAMESPACE # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ inline namespace v12 { # define FMT_END_NAMESPACE \ } \ } #endif #ifndef FMT_EXPORT # define FMT_EXPORT # define FMT_BEGIN_EXPORT # define FMT_END_EXPORT #endif #ifdef _WIN32 # define FMT_WIN32 1 #else # define FMT_WIN32 0 #endif #if !defined(FMT_HEADER_ONLY) && FMT_WIN32 # if defined(FMT_LIB_EXPORT) # define FMT_API __declspec(dllexport) # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # endif #elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) # define FMT_API FMT_VISIBILITY("default") #endif #ifndef FMT_API # define FMT_API #endif #ifndef FMT_OPTIMIZE_SIZE # define FMT_OPTIMIZE_SIZE 0 #endif // FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher // per-call binary size by passing built-in types through the extension API. #ifndef FMT_BUILTIN_TYPES # define FMT_BUILTIN_TYPES 1 #endif #define FMT_APPLY_VARIADIC(expr) \ using unused = int[]; \ (void)unused { 0, (expr, 0)... } FMT_BEGIN_NAMESPACE // 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_const_t = typename std::remove_const::type; template using remove_cvref_t = typename std::remove_cv>::type; template using make_unsigned_t = typename std::make_unsigned::type; template using underlying_t = typename std::underlying_type::type; template using decay_t = typename std::decay::type; using nullptr_t = decltype(nullptr); #if (FMT_GCC_VERSION && FMT_GCC_VERSION < 500) || FMT_MSC_VERSION // A workaround for gcc 4.9 & MSVC v141 to make void_t work in a SFINAE context. template struct void_t_impl { using type = void; }; template using void_t = typename void_t_impl::type; #else template using void_t = void; #endif 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(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 #endif template constexpr auto min_of(T a, T b) -> T { return a < b ? a : b; } template constexpr auto max_of(T a, T b) -> T { return a > b ? a : b; } FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); namespace detail { // Suppresses "unused variable" warnings with the method described in // https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. // (void)var does not work on many Intel compilers. template FMT_CONSTEXPR void ignore_unused(const T&...) {} constexpr auto is_constant_evaluated(bool default_value = false) noexcept -> bool { // Workaround for incompatibility between clang 14 and libstdc++ consteval-based // std::is_constant_evaluated: https://github.com/fmtlib/fmt/issues/3247. #if FMT_CPLUSPLUS >= 202002L && FMT_GLIBCXX_RELEASE >= 12 && \ (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) ignore_unused(default_value); return __builtin_is_constant_evaluated(); #elif defined(__cpp_lib_is_constant_evaluated) ignore_unused(default_value); return std::is_constant_evaluated(); #else return default_value; #endif } // Suppresses "conditional expression is constant" warnings. template FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T { return val; } FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); #if defined(FMT_ASSERT) // Use the provided definition. #elif defined(NDEBUG) // FMT_ASSERT is not empty to avoid -Wempty-body. # define FMT_ASSERT(condition, message) \ fmt::detail::ignore_unused((condition), (message)) #else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ? (void)0 \ : ::fmt::assert_fail(__FILE__, __LINE__, (message))) #endif #ifdef FMT_USE_INT128 // Use the provided definition. #elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ !(FMT_CLANG_VERSION && FMT_MSC_VERSION) # define FMT_USE_INT128 1 using int128_opt = __int128_t; // An optional native 128-bit integer. using uint128_opt = __uint128_t; inline auto map(int128_opt x) -> int128_opt { return x; } inline auto map(uint128_opt x) -> uint128_opt { return x; } #else # define FMT_USE_INT128 0 #endif #if !FMT_USE_INT128 enum class int128_opt {}; enum class uint128_opt {}; // Reduce template instantiations. inline auto map(int128_opt) -> monostate { return {}; } inline auto map(uint128_opt) -> monostate { return {}; } #endif #ifdef FMT_USE_BITINT // Use the provided definition. #elif FMT_CLANG_VERSION >= 1500 && !defined(__CUDACC__) # define FMT_USE_BITINT 1 #else # define FMT_USE_BITINT 0 #endif #if FMT_USE_BITINT FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension") template using bitint = _BitInt(N); template using ubitint = unsigned _BitInt(N); #else template struct bitint {}; template struct ubitint {}; #endif // FMT_USE_BITINT // Casts a nonnegative integer to unsigned. template FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); return static_cast>(value); } template using unsigned_char = conditional_t; // A heuristic to detect std::string and std::[experimental::]string_view. // It is mainly used to avoid dependency on <[experimental/]string_view>. template struct is_std_string_like : std::false_type {}; template struct is_std_string_like().find_first_of( typename T::value_type(), 0))>> : std::is_convertible().data()), const typename T::value_type*> {}; // Check if the literal encoding is UTF-8. enum { is_utf8_enabled = "\u00A7"[1] == '\xA7' }; enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled }; #ifndef FMT_UNICODE # define FMT_UNICODE 1 #endif static_assert(!FMT_UNICODE || use_utf8, "Unicode support requires compiling with /utf-8"); template constexpr auto narrow(T*) -> char* { return nullptr; } constexpr FMT_ALWAYS_INLINE auto narrow(const char* s) -> const char* { return s; } template FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, size_t n) -> int { if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); for (; n != 0; ++s1, ++s2, --n) { if (*s1 < *s2) return -1; if (*s1 > *s2) return 1; } return 0; } namespace adl { using namespace std; template auto invoke_back_inserter() -> decltype(back_inserter(std::declval())); } // namespace adl template struct is_back_insert_iterator : std::false_type {}; template struct is_back_insert_iterator< It, bool_constant()), It>::value>> : std::true_type {}; // Extracts a reference to the container from *insert_iterator. template inline FMT_CONSTEXPR20 auto get_container(OutputIt it) -> typename OutputIt::container_type& { struct accessor : OutputIt { FMT_CONSTEXPR20 accessor(OutputIt base) : OutputIt(base) {} using OutputIt::container; }; return *accessor(it).container; } } // namespace detail // Parsing-related public API and forward declarations. FMT_BEGIN_EXPORT /** * 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::basic_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() noexcept : data_(nullptr), size_(0) {} /// Constructs a string view object from a C string and a size. constexpr basic_string_view(const Char* s, size_t count) noexcept : data_(s), size_(count) {} constexpr basic_string_view(nullptr_t) = delete; /// Constructs a string view object from a C string. #if FMT_GCC_VERSION FMT_ALWAYS_INLINE #endif FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { #if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION if (std::is_same::value && !detail::is_constant_evaluated()) { size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr. return; } #endif size_t len = 0; while (*s++) ++len; size_ = len; } /// Constructs a string view from a `std::basic_string` or a /// `std::basic_string_view` object. template ::value&& std::is_same< typename S::value_type, Char>::value)> FMT_CONSTEXPR basic_string_view(const S& s) noexcept : data_(s.data()), size_(s.size()) {} /// Returns a pointer to the string data. constexpr auto data() const noexcept -> const Char* { return data_; } /// Returns the string size. constexpr auto size() const noexcept -> size_t { return size_; } constexpr auto begin() const noexcept -> iterator { return data_; } constexpr auto end() const noexcept -> iterator { return data_ + size_; } constexpr auto operator[](size_t pos) const noexcept -> const Char& { return data_[pos]; } FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { data_ += n; size_ -= n; } FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept -> bool { return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0; } FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool { return size_ >= 1 && *data_ == c; } FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool { return starts_with(basic_string_view(s)); } FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { int result = detail::compare(data_, other.data_, min_of(size_, other.size_)); if (result != 0) return result; return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); } FMT_CONSTEXPR 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; template class basic_appender; using appender = basic_appender; // Checks whether T is a container with contiguous storage. template struct is_contiguous : std::false_type {}; class context; template class generic_context; template class parse_context; // Longer aliases for C++20 compatibility. template using basic_format_parse_context = parse_context; using format_parse_context = parse_context; template using basic_format_context = conditional_t::value, context, generic_context>; using format_context = context; template using buffered_context = conditional_t::value, context, generic_context, Char>>; template class basic_format_arg; template class 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; // A formatter for objects of type T. template struct formatter { // A deleted default constructor indicates a disabled formatter. formatter() = delete; }; /// Reports a format error at compile time or, via a `format_error` exception, /// at runtime. // This function is intentionally not constexpr to give a compile-time error. FMT_NORETURN FMT_API void report_error(const char* message); enum class presentation_type : unsigned char { // Common specifiers: none = 0, debug = 1, // '?' string = 2, // 's' (string, bool) // Integral, bool and character specifiers: dec = 3, // 'd' hex, // 'x' or 'X' oct, // 'o' bin, // 'b' or 'B' chr, // 'c' // String and pointer specifiers: pointer = 3, // 'p' // Floating-point specifiers: exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) fixed, // 'f' or 'F' general, // 'g' or 'G' hexfloat // 'a' or 'A' }; enum class align { none, left, right, center, numeric }; enum class sign { none, minus, plus, space }; enum class arg_id_kind { none, index, name }; // Basic format specifiers for built-in and string types. class basic_specs { private: // Data is arranged as follows: // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // |type |align| w | p | s |u|#|L| f | unused | // +-----+-----+---+---+---+-+-+-+-----+---------------------------+ // // w - dynamic width info // p - dynamic precision info // s - sign // u - uppercase (e.g. 'X' for 'x') // # - alternate form ('#') // L - localized // f - fill size // // Bitfields are not used because of compiler bugs such as gcc bug 61414. enum : unsigned { type_mask = 0x00007, align_mask = 0x00038, width_mask = 0x000C0, precision_mask = 0x00300, sign_mask = 0x00C00, uppercase_mask = 0x01000, alternate_mask = 0x02000, localized_mask = 0x04000, fill_size_mask = 0x38000, align_shift = 3, width_shift = 6, precision_shift = 8, sign_shift = 10, fill_size_shift = 15, max_fill_size = 4 }; unsigned data_ = 1 << fill_size_shift; static_assert(sizeof(basic_specs::data_) * CHAR_BIT >= 18, ""); // Character (code unit) type is erased to prevent template bloat. char fill_data_[max_fill_size] = {' '}; FMT_CONSTEXPR void set_fill_size(size_t size) { data_ = (data_ & ~fill_size_mask) | (static_cast(size) << fill_size_shift); } public: constexpr auto type() const -> presentation_type { return static_cast(data_ & type_mask); } FMT_CONSTEXPR void set_type(presentation_type t) { data_ = (data_ & ~type_mask) | static_cast(t); } constexpr auto align() const -> align { return static_cast((data_ & align_mask) >> align_shift); } FMT_CONSTEXPR void set_align(fmt::align a) { data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); } constexpr auto dynamic_width() const -> arg_id_kind { return static_cast((data_ & width_mask) >> width_shift); } FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) { data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); } FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind { return static_cast((data_ & precision_mask) >> precision_shift); } FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) { data_ = (data_ & ~precision_mask) | (static_cast(p) << precision_shift); } constexpr auto dynamic() const -> bool { return (data_ & (width_mask | precision_mask)) != 0; } constexpr auto sign() const -> sign { return static_cast((data_ & sign_mask) >> sign_shift); } FMT_CONSTEXPR void set_sign(fmt::sign s) { data_ = (data_ & ~sign_mask) | (static_cast(s) << sign_shift); } constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; } constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; } FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; } FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; } constexpr auto localized() const -> bool { return (data_ & localized_mask) != 0; } FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; } constexpr auto fill_size() const -> size_t { return (data_ & fill_size_mask) >> fill_size_shift; } template ::value)> constexpr auto fill() const -> const Char* { return fill_data_; } template ::value)> constexpr auto fill() const -> const Char* { return nullptr; } template constexpr auto fill_unit() const -> Char { using uchar = unsigned char; return static_cast(static_cast(fill_data_[0]) | (static_cast(fill_data_[1]) << 8) | (static_cast(fill_data_[2]) << 16)); } FMT_CONSTEXPR void set_fill(char c) { fill_data_[0] = c; set_fill_size(1); } template FMT_CONSTEXPR void set_fill(basic_string_view s) { auto size = s.size(); set_fill_size(size); if (size == 1) { unsigned uchar = static_cast>(s[0]); fill_data_[0] = static_cast(uchar); fill_data_[1] = static_cast(uchar >> 8); fill_data_[2] = static_cast(uchar >> 16); return; } FMT_ASSERT(size <= max_fill_size, "invalid fill"); for (size_t i = 0; i < size; ++i) fill_data_[i & 3] = static_cast(s[i]); } FMT_CONSTEXPR void copy_fill_from(const basic_specs& specs) { set_fill_size(specs.fill_size()); for (size_t i = 0; i < max_fill_size; ++i) fill_data_[i] = specs.fill_data_[i]; } }; // Format specifiers for built-in and string types. struct format_specs : basic_specs { int width; int precision; constexpr format_specs() : width(0), precision(-1) {} }; /** * Parsing context consisting of a format string range being parsed and an * argument counter for automatic indexing. */ template class parse_context { private: basic_string_view fmt_; int next_arg_id_; enum { use_constexpr_cast = !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200 }; FMT_CONSTEXPR void do_check_arg_id(int arg_id); public: using char_type = Char; using iterator = const Char*; constexpr explicit parse_context(basic_string_view fmt, int next_arg_id = 0) : fmt_(fmt), next_arg_id_(next_arg_id) {} /// Returns an iterator to the beginning of the format string range being /// parsed. constexpr auto begin() const noexcept -> iterator { return fmt_.begin(); } /// Returns an iterator past the end of the format string range being parsed. constexpr auto end() const noexcept -> iterator { return fmt_.end(); } /// Advances the begin iterator to `it`. FMT_CONSTEXPR void advance_to(iterator it) { fmt_.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 { if (next_arg_id_ < 0) { report_error("cannot switch from manual to automatic argument indexing"); return 0; } int id = next_arg_id_++; do_check_arg_id(id); return id; } /// Reports an error if using the automatic argument indexing; otherwise /// switches to the manual indexing. FMT_CONSTEXPR void check_arg_id(int id) { if (next_arg_id_ > 0) { report_error("cannot switch from automatic to manual argument indexing"); return; } next_arg_id_ = -1; do_check_arg_id(id); } FMT_CONSTEXPR void check_arg_id(basic_string_view) { next_arg_id_ = -1; } FMT_CONSTEXPR void check_dynamic_spec(int arg_id); }; #ifndef FMT_USE_LOCALE # define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1) #endif // A type-erased reference to std::locale to avoid the heavy include. class locale_ref { #if FMT_USE_LOCALE private: const void* locale_; // A type-erased pointer to std::locale. public: constexpr locale_ref() : locale_(nullptr) {} template locale_ref(const Locale& loc) : locale_(&loc) { // Check if std::isalpha is found via ADL to reduce the chance of misuse. isalpha('x', loc); } inline explicit operator bool() const noexcept { return locale_ != nullptr; } #endif // FMT_USE_LOCALE public: template auto get() const -> Locale; }; FMT_END_EXPORT namespace detail { // Specifies if `T` is a code unit type. template struct is_code_unit : std::false_type {}; template <> struct is_code_unit : std::true_type {}; template <> struct is_code_unit : std::true_type {}; template <> struct is_code_unit : std::true_type {}; template <> struct is_code_unit : std::true_type {}; #ifdef __cpp_char8_t template <> struct is_code_unit : bool_constant {}; #endif // Constructs fmt::basic_string_view from types implicitly convertible // to it, deducing Char. Explicitly convertible types such as the ones returned // from FMT_STRING are intentionally excluded. template ::value)> constexpr auto to_string_view(const Char* s) -> basic_string_view { return s; } template ::value)> constexpr auto to_string_view(const T& s) -> basic_string_view { return s; } template constexpr auto to_string_view(basic_string_view s) -> basic_string_view { return s; } template struct has_to_string_view : std::false_type {}; // detail:: is intentional since to_string_view is not an extension point. template struct has_to_string_view< T, void_t()))>> : std::true_type {}; /// String's character (code unit) type. detail:: is intentional to prevent ADL. template ()))> using char_t = typename V::value_type; 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_opt, int128_type); FMT_TYPE_CONSTANT(uint128_opt, 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 auto is_integral_type(type t) -> bool { return t > type::none_type && t <= type::last_integer_type; } constexpr auto is_arithmetic_type(type t) -> bool { return t > type::none_type && t <= type::last_numeric_type; } constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } constexpr auto in(type t, int set) -> bool { return ((set >> static_cast(t)) & 1) != 0; } // Bitsets of types. enum { sint_set = set(type::int_type) | set(type::long_long_type) | set(type::int128_type), uint_set = set(type::uint_type) | set(type::ulong_long_type) | set(type::uint128_type), bool_set = set(type::bool_type), char_set = set(type::char_type), float_set = set(type::float_type) | set(type::double_type) | set(type::long_double_type), string_set = set(type::string_type), cstring_set = set(type::cstring_type), pointer_set = set(type::pointer_type) }; struct view {}; template struct is_view : std::false_type {}; template struct is_view> : std::is_base_of {}; template struct named_arg; template struct is_named_arg : std::false_type {}; template struct is_static_named_arg : std::false_type {}; template struct is_named_arg> : std::true_type {}; template struct named_arg : view { const Char* name; const T& value; named_arg(const Char* n, const T& v) : name(n), value(v) {} static_assert(!is_named_arg::value, "nested named arguments"); }; template constexpr auto count() -> int { return B ? 1 : 0; } template constexpr auto count() -> int { return (B1 ? 1 : 0) + count(); } template constexpr auto count_named_args() -> int { return count::value...>(); } template constexpr auto count_static_named_args() -> int { return count::value...>(); } template struct named_arg_info { const Char* name; int id; }; // named_args is non-const to suppress a bogus -Wmaybe-uninitialized in gcc 13. template FMT_CONSTEXPR void check_for_duplicate(named_arg_info* named_args, int named_arg_index, basic_string_view arg_name) { for (int i = 0; i < named_arg_index; ++i) { if (named_args[i].name == arg_name) report_error("duplicate named arg"); } } template ::value)> void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { ++arg_index; } template ::value)> void init_named_arg(named_arg_info* named_args, int& arg_index, int& named_arg_index, const T& arg) { check_for_duplicate(named_args, named_arg_index, arg.name); named_args[named_arg_index++] = {arg.name, arg_index++}; } template ::value)> FMT_CONSTEXPR void init_static_named_arg(named_arg_info*, int& arg_index, int&) { ++arg_index; } template ::value)> FMT_CONSTEXPR void init_static_named_arg(named_arg_info* named_args, int& arg_index, int& named_arg_index) { check_for_duplicate(named_args, named_arg_index, T::name); named_args[named_arg_index++] = {T::name, arg_index++}; } // 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) && FMT_BUILTIN_TYPES }; using long_type = conditional_t; using ulong_type = conditional_t; template using format_as_result = remove_cvref_t()))>; template using format_as_member_result = remove_cvref_t::format_as(std::declval()))>; template struct use_format_as : std::false_type {}; // format_as member is only used to avoid injection into the std namespace. template struct use_format_as_member : std::false_type {}; // Only map owning types because mapping views can be unsafe. template struct use_format_as< T, bool_constant>::value>> : std::true_type {}; template struct use_format_as_member< T, bool_constant>::value>> : std::true_type {}; template > using use_formatter = bool_constant<(std::is_class::value || std::is_enum::value || std::is_union::value || std::is_array::value) && !has_to_string_view::value && !is_named_arg::value && !use_format_as::value && !use_format_as_member::value>; template > auto has_formatter_impl(T* p, buffered_context* ctx = nullptr) -> decltype(formatter().format(*p, *ctx), std::true_type()); template auto has_formatter_impl(...) -> std::false_type; // T can be const-qualified to check if it is const-formattable. template constexpr auto has_formatter() -> bool { return decltype(has_formatter_impl(static_cast(nullptr)))::value; } // Maps formatting argument types to natively supported types or user-defined // types with formatters. Returns void on errors to be SFINAE-friendly. template struct type_mapper { static auto map(signed char) -> int; static auto map(unsigned char) -> unsigned; static auto map(short) -> int; static auto map(unsigned short) -> unsigned; static auto map(int) -> int; static auto map(unsigned) -> unsigned; static auto map(long) -> long_type; static auto map(unsigned long) -> ulong_type; static auto map(long long) -> long long; static auto map(unsigned long long) -> unsigned long long; static auto map(int128_opt) -> int128_opt; static auto map(uint128_opt) -> uint128_opt; static auto map(bool) -> bool; template static auto map(bitint) -> conditional_t; template static auto map(ubitint) -> conditional_t; template ::value)> static auto map(T) -> conditional_t< std::is_same::value || std::is_same::value, Char, void>; static auto map(float) -> float; static auto map(double) -> double; static auto map(long double) -> long double; static auto map(Char*) -> const Char*; static auto map(const Char*) -> const Char*; template , FMT_ENABLE_IF(!std::is_pointer::value)> static auto map(const T&) -> conditional_t::value, basic_string_view, void>; static auto map(void*) -> const void*; static auto map(const void*) -> const void*; static auto map(volatile void*) -> const void*; static auto map(const volatile void*) -> const void*; static auto map(nullptr_t) -> const void*; template ::value || std::is_member_pointer::value)> static auto map(const T&) -> void; template ::value)> static auto map(const T& x) -> decltype(map(format_as(x))); template ::value)> static auto map(const T& x) -> decltype(map(formatter::format_as(x))); template ::value)> static auto map(T&) -> conditional_t(), T&, void>; template ::value)> static auto map(const T& named_arg) -> decltype(map(named_arg.value)); }; // detail:: is used to workaround a bug in MSVC 2017. template using mapped_t = decltype(detail::type_mapper::map(std::declval())); // A type constant after applying type_mapper. template using mapped_type_constant = type_constant, Char>; template ::value> using stored_type_constant = std::integral_constant< type, Context::builtin_types || TYPE == type::int_type ? TYPE : type::custom_type>; // A parse context with extra data used only in compile-time checks. template class compile_parse_context : public parse_context { private: int num_args_; const type* types_; using base = parse_context; public: FMT_CONSTEXPR explicit compile_parse_context(basic_string_view fmt, int num_args, const type* types, int next_arg_id = 0) : base(fmt, next_arg_id), num_args_(num_args), types_(types) {} constexpr auto num_args() const -> int { return num_args_; } constexpr auto arg_type(int id) const -> type { return types_[id]; } FMT_CONSTEXPR auto next_arg_id() -> int { int id = base::next_arg_id(); if (id >= num_args_) report_error("argument not found"); return id; } FMT_CONSTEXPR void check_arg_id(int id) { base::check_arg_id(id); if (id >= num_args_) report_error("argument not found"); } using base::check_arg_id; FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { ignore_unused(arg_id); if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) report_error("width/precision is not integer"); } }; // An argument reference. template union arg_ref { FMT_CONSTEXPR arg_ref(int idx = 0) : index(idx) {} FMT_CONSTEXPR arg_ref(basic_string_view n) : name(n) {} int index; basic_string_view name; }; // Format specifiers with width and precision resolved at formatting rather // than parsing time to allow reusing the same parsed specifiers with // different sets of arguments (precompilation of format strings). template struct dynamic_format_specs : format_specs { arg_ref width_ref; arg_ref precision_ref; }; // Converts a character to ASCII. Returns '\0' on conversion failure. template ::value)> constexpr auto to_ascii(Char c) -> char { return c <= 0xff ? static_cast(c) : '\0'; } // Returns the number of code units in a code point or 1 on error. template FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { if (const_check(sizeof(Char) != 1)) return 1; auto c = static_cast(*begin); return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; } // 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; int digits10 = static_cast(sizeof(int) * CHAR_BIT * 3 / 10); if (num_digits <= digits10) return static_cast(value); // Check for overflow. unsigned max = INT_MAX; return num_digits == digits10 + 1 && prev * 10ull + unsigned(p[-1] - '0') <= max ? static_cast(value) : error_value; } FMT_CONSTEXPR inline auto parse_align(char c) -> align { switch (c) { case '<': return align::left; case '>': return align::right; case '^': return align::center; } return align::none; } template constexpr auto is_name_start(Char c) -> bool { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; } template FMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end, Handler&& handler) -> const Char* { Char c = *begin; if (c >= '0' && c <= '9') { int index = 0; if (c != '0') index = parse_nonnegative_int(begin, end, INT_MAX); else ++begin; if (begin == end || (*begin != '}' && *begin != ':')) report_error("invalid format string"); else handler.on_index(index); return begin; } if (FMT_OPTIMIZE_SIZE > 1 || !is_name_start(c)) { report_error("invalid format string"); return begin; } auto it = begin; do { ++it; } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); handler.on_name({begin, to_unsigned(it - begin)}); return it; } template struct dynamic_spec_handler { parse_context& ctx; arg_ref& ref; arg_id_kind& kind; FMT_CONSTEXPR void on_index(int id) { ref = id; kind = arg_id_kind::index; ctx.check_arg_id(id); ctx.check_dynamic_spec(id); } FMT_CONSTEXPR void on_name(basic_string_view id) { ref = id; kind = arg_id_kind::name; ctx.check_arg_id(id); } }; template struct parse_dynamic_spec_result { const Char* end; arg_id_kind kind; }; // Parses integer | "{" [arg_id] "}". template FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, int& value, arg_ref& ref, parse_context& ctx) -> parse_dynamic_spec_result { FMT_ASSERT(begin != end, ""); auto kind = arg_id_kind::none; if ('0' <= *begin && *begin <= '9') { int val = parse_nonnegative_int(begin, end, -1); if (val == -1) report_error("number is too big"); value = val; } else { if (*begin == '{') { ++begin; if (begin != end) { Char c = *begin; if (c == '}' || c == ':') { int id = ctx.next_arg_id(); ref = id; kind = arg_id_kind::index; ctx.check_dynamic_spec(id); } else { begin = parse_arg_id(begin, end, dynamic_spec_handler{ctx, ref, kind}); } } if (begin != end && *begin == '}') return {++begin, kind}; } report_error("invalid format string"); } return {begin, kind}; } template FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, format_specs& specs, arg_ref& width_ref, parse_context& ctx) -> const Char* { auto result = parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); specs.set_dynamic_width(result.kind); return result.end; } template FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, format_specs& specs, arg_ref& precision_ref, parse_context& ctx) -> const Char* { ++begin; if (begin == end) { report_error("invalid precision"); return begin; } auto result = parse_dynamic_spec(begin, end, specs.precision, precision_ref, ctx); specs.set_dynamic_precision(result.kind); return result.end; } enum class state { start, align, sign, hash, zero, width, precision, locale }; // Parses standard format specifiers. template FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, dynamic_format_specs& specs, parse_context& ctx, type arg_type) -> const Char* { auto c = '\0'; if (end - begin > 1) { auto next = to_ascii(begin[1]); c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; } else { if (begin == end) return begin; c = to_ascii(*begin); } struct { state current_state = state::start; FMT_CONSTEXPR void operator()(state s, bool valid = true) { if (current_state >= s || !valid) report_error("invalid format specifier"); current_state = s; } } enter_state; using pres = presentation_type; constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; struct { const Char*& begin; format_specs& specs; type arg_type; FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { if (!in(arg_type, set)) report_error("invalid format specifier"); specs.set_type(pres_type); return begin + 1; } } parse_presentation_type{begin, specs, arg_type}; for (;;) { switch (c) { case '<': case '>': case '^': enter_state(state::align); specs.set_align(parse_align(c)); ++begin; break; case '+': case ' ': specs.set_sign(c == ' ' ? sign::space : sign::plus); FMT_FALLTHROUGH; case '-': enter_state(state::sign, in(arg_type, sint_set | float_set)); ++begin; break; case '#': enter_state(state::hash, is_arithmetic_type(arg_type)); specs.set_alt(); ++begin; break; case '0': enter_state(state::zero); if (!is_arithmetic_type(arg_type)) report_error("format specifier requires numeric argument"); if (specs.align() == align::none) { // Ignore 0 if align is specified for compatibility with std::format. specs.set_align(align::numeric); specs.set_fill('0'); } ++begin; break; // clang-format off case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '{': // clang-format on enter_state(state::width); begin = parse_width(begin, end, specs, specs.width_ref, ctx); break; case '.': enter_state(state::precision, in(arg_type, float_set | string_set | cstring_set)); begin = parse_precision(begin, end, specs, specs.precision_ref, ctx); break; case 'L': enter_state(state::locale, is_arithmetic_type(arg_type)); specs.set_localized(); ++begin; break; case 'd': return parse_presentation_type(pres::dec, integral_set); case 'X': specs.set_upper(); FMT_FALLTHROUGH; case 'x': return parse_presentation_type(pres::hex, integral_set); case 'o': return parse_presentation_type(pres::oct, integral_set); case 'B': specs.set_upper(); FMT_FALLTHROUGH; case 'b': return parse_presentation_type(pres::bin, integral_set); case 'E': specs.set_upper(); FMT_FALLTHROUGH; case 'e': return parse_presentation_type(pres::exp, float_set); case 'F': specs.set_upper(); FMT_FALLTHROUGH; case 'f': return parse_presentation_type(pres::fixed, float_set); case 'G': specs.set_upper(); FMT_FALLTHROUGH; case 'g': return parse_presentation_type(pres::general, float_set); case 'A': specs.set_upper(); FMT_FALLTHROUGH; case 'a': return parse_presentation_type(pres::hexfloat, float_set); case 'c': if (arg_type == type::bool_type) report_error("invalid format specifier"); return parse_presentation_type(pres::chr, integral_set); case 's': return parse_presentation_type(pres::string, bool_set | string_set | cstring_set); case 'p': return parse_presentation_type(pres::pointer, pointer_set | cstring_set); case '?': return parse_presentation_type(pres::debug, char_set | string_set | cstring_set); case '}': return begin; default: { if (*begin == '}') return begin; // Parse fill and alignment. auto fill_end = begin + code_point_length(begin); if (end - fill_end <= 0) { report_error("invalid format specifier"); return begin; } if (*begin == '{') { report_error("invalid fill character '{'"); return begin; } auto alignment = parse_align(to_ascii(*fill_end)); enter_state(state::align, alignment != align::none); specs.set_fill( basic_string_view(begin, to_unsigned(fill_end - begin))); specs.set_align(alignment); begin = fill_end + 1; } } if (begin == end) return begin; c = to_ascii(*begin); } } template FMT_CONSTEXPR FMT_INLINE auto parse_replacement_field(const Char* begin, const Char* end, Handler&& handler) -> const Char* { ++begin; if (begin == end) { handler.on_error("invalid format string"); return end; } int arg_id = 0; switch (*begin) { case '}': handler.on_replacement_field(handler.on_arg_id(), begin); return begin + 1; case '{': handler.on_text(begin, begin + 1); return begin + 1; case ':': arg_id = handler.on_arg_id(); break; default: { struct id_adapter { Handler& handler; int arg_id; FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } FMT_CONSTEXPR void on_name(basic_string_view id) { arg_id = handler.on_arg_id(id); } } adapter = {handler, 0}; begin = parse_arg_id(begin, end, adapter); arg_id = adapter.arg_id; Char c = begin != end ? *begin : Char(); if (c == '}') { handler.on_replacement_field(arg_id, begin); return begin + 1; } if (c != ':') { handler.on_error("missing '}' in format string"); return end; } break; } } begin = handler.on_format_specs(arg_id, begin + 1, end); if (begin == end || *begin != '}') return handler.on_error("unknown format specifier"), end; return begin + 1; } template FMT_CONSTEXPR void parse_format_string(basic_string_view fmt, Handler&& handler) { auto begin = fmt.data(), end = begin + fmt.size(); auto 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); } // Checks char specs and returns true iff the presentation type is char-like. FMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool { auto type = specs.type(); if (type != presentation_type::none && type != presentation_type::chr && type != presentation_type::debug) { return false; } if (specs.align() == align::numeric || specs.sign() != sign::none || specs.alt()) { report_error("invalid format specifier for char"); } return true; } // A base class for compile-time strings. struct compile_string {}; template FMT_VISIBILITY("hidden") // Suppress an ld warning on macOS (#3769). FMT_CONSTEXPR auto invoke_parse(parse_context& ctx) -> const Char* { using mapped_type = remove_cvref_t>; constexpr bool formattable = std::is_constructible>::value; if (!formattable) return ctx.begin(); // Error is reported in the value ctor. using formatted_type = conditional_t; return formatter().parse(ctx); } template struct arg_pack {}; template class format_string_checker { private: type types_[max_of(1, NUM_ARGS)]; named_arg_info named_args_[max_of(1, NUM_NAMED_ARGS)]; compile_parse_context context_; using parse_func = auto (*)(parse_context&) -> const Char*; parse_func parse_funcs_[max_of(1, NUM_ARGS)]; public: template FMT_CONSTEXPR explicit format_string_checker(basic_string_view fmt, arg_pack) : types_{mapped_type_constant::value...}, named_args_{}, context_(fmt, NUM_ARGS, types_), parse_funcs_{&invoke_parse...} { int arg_index = 0, named_arg_index = 0; FMT_APPLY_VARIADIC( init_static_named_arg(named_args_, arg_index, named_arg_index)); ignore_unused(arg_index, named_arg_index); } 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 { context_.check_arg_id(id); return id; } FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { for (int i = 0; i < NUM_NAMED_ARGS; ++i) { if (named_args_[i].name == id) return named_args_[i].id; } if (!DYNAMIC_NAMES) on_error("argument not found"); return -1; } FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { on_format_specs(id, begin, begin); // Call parse() on empty specs. } FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char* end) -> const Char* { context_.advance_to(begin); if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); // If id is out of range, it means we do not know the type and cannot parse // the format at compile time. Instead, skip over content until we finish // the format spec, accounting for any nested replacements. for (int bracket_count = 0; begin != end && (bracket_count > 0 || *begin != '}'); ++begin) { if (*begin == '{') ++bracket_count; else if (*begin == '}') --bracket_count; } return begin; } FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) { report_error(message); } }; /// A contiguous memory buffer with an optional growing ability. It is an /// internal class and shouldn't be used directly, only via `memory_buffer`. template class buffer { private: T* ptr_; size_t size_; size_t capacity_; using grow_fun = void (*)(buffer& buf, size_t capacity); grow_fun grow_; protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. FMT_MSC_WARNING(suppress : 26495) FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept : size_(sz), capacity_(sz), grow_(grow) {} constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept : ptr_(p), size_(sz), capacity_(cap), grow_(grow) {} FMT_CONSTEXPR20 ~buffer() = default; buffer(buffer&&) = default; /// Sets the buffer data and capacity. FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { ptr_ = buf_data; capacity_ = buf_capacity; } public: using value_type = T; using const_reference = const T&; buffer(const buffer&) = delete; void operator=(const buffer&) = delete; auto begin() noexcept -> T* { return ptr_; } auto end() noexcept -> T* { return ptr_ + size_; } auto begin() const noexcept -> const T* { return ptr_; } auto end() const noexcept -> const T* { return ptr_ + size_; } /// Returns the size of this buffer. constexpr auto size() const noexcept -> size_t { return size_; } /// Returns the capacity of this buffer. constexpr auto capacity() const noexcept -> size_t { return capacity_; } /// Returns a pointer to the buffer data (not null-terminated). FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } /// Clears this buffer. FMT_CONSTEXPR 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. FMT_CONSTEXPR void try_resize(size_t count) { try_reserve(count); size_ = min_of(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. FMT_CONSTEXPR void try_reserve(size_t new_capacity) { if (new_capacity > capacity_) grow_(*this, new_capacity); } FMT_CONSTEXPR void push_back(const T& value) { try_reserve(size_ + 1); ptr_[size_++] = value; } /// Appends data to the end of the buffer. template // Workaround for MSVC2019 to fix error C2893: Failed to specialize function // template 'void fmt::v11::detail::buffer::append(const U *,const U *)'. #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940 FMT_CONSTEXPR20 #endif void append(const U* begin, const U* end) { while (begin != end) { auto size = size_; auto free_cap = capacity_ - size; auto count = to_unsigned(end - begin); if (free_cap < count) { grow_(*this, size + count); size = size_; free_cap = capacity_ - size; count = count < free_cap ? count : free_cap; } // A loop is faster than memcpy on small sizes. T* out = ptr_ + size; for (size_t i = 0; i < count; ++i) out[i] = begin[i]; size_ += count; begin += count; } } template FMT_CONSTEXPR auto operator[](Idx index) -> T& { return ptr_[index]; } template FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { return ptr_[index]; } }; struct buffer_traits { constexpr explicit buffer_traits(size_t) {} constexpr auto count() const -> size_t { return 0; } constexpr auto limit(size_t size) const -> size_t { return size; } }; class fixed_buffer_traits { private: size_t count_ = 0; size_t limit_; public: constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} constexpr auto count() const -> size_t { return count_; } FMT_CONSTEXPR auto limit(size_t size) -> size_t { size_t n = limit_ > count_ ? limit_ - count_ : 0; count_ += size; return min_of(size, n); } }; // A buffer that writes to an output iterator when flushed. template class iterator_buffer : public Traits, public buffer { private: OutputIt out_; enum { buffer_size = 256 }; T data_[buffer_size]; static FMT_CONSTEXPR void grow(buffer& buf, size_t) { if (buf.size() == buffer_size) static_cast(buf).flush(); } void flush() { auto size = this->size(); this->clear(); const T* begin = data_; const T* end = begin + this->limit(size); while (begin != end) *out_++ = *begin++; } public: explicit iterator_buffer(OutputIt out, size_t n = buffer_size) : Traits(n), buffer(grow, data_, 0, buffer_size), out_(out) {} iterator_buffer(iterator_buffer&& other) noexcept : Traits(other), buffer(grow, data_, 0, buffer_size), out_(other.out_) {} ~iterator_buffer() { // Don't crash if flush fails during unwinding. FMT_TRY { flush(); } FMT_CATCH(...) {} } auto out() -> OutputIt { flush(); return out_; } auto count() const -> size_t { return Traits::count() + this->size(); } }; template class iterator_buffer : public fixed_buffer_traits, public buffer { private: T* out_; enum { buffer_size = 256 }; T data_[buffer_size]; static FMT_CONSTEXPR void grow(buffer& buf, size_t) { if (buf.size() == buf.capacity()) static_cast(buf).flush(); } void flush() { size_t n = this->limit(this->size()); if (this->data() == out_) { out_ += n; this->set(data_, buffer_size); } this->clear(); } public: explicit iterator_buffer(T* out, size_t n = buffer_size) : fixed_buffer_traits(n), buffer(grow, out, 0, n), out_(out) {} iterator_buffer(iterator_buffer&& other) noexcept : fixed_buffer_traits(other), buffer(static_cast(other)), out_(other.out_) { if (this->data() != out_) { this->set(data_, buffer_size); this->clear(); } } ~iterator_buffer() { flush(); } auto out() -> T* { flush(); return out_; } auto count() const -> size_t { return fixed_buffer_traits::count() + this->size(); } }; template class iterator_buffer : public buffer { public: explicit iterator_buffer(T* out, size_t = 0) : buffer([](buffer&, size_t) {}, out, 0, ~size_t()) {} auto out() -> T* { return &*this->end(); } }; template class container_buffer : public buffer { private: using value_type = typename Container::value_type; static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { auto& self = static_cast(buf); self.container.resize(capacity); self.set(&self.container[0], capacity); } public: Container& container; explicit container_buffer(Container& c) : buffer(grow, c.size()), container(c) {} }; // A buffer that writes to a container with the contiguous storage. template class iterator_buffer< OutputIt, enable_if_t::value && is_contiguous::value, typename OutputIt::container_type::value_type>> : public container_buffer { private: using base = container_buffer; public: explicit iterator_buffer(typename OutputIt::container_type& c) : base(c) {} explicit iterator_buffer(OutputIt out, size_t = 0) : base(get_container(out)) {} auto out() -> OutputIt { return OutputIt(this->container); } }; // A buffer that counts the number of code units written discarding the output. template class counting_buffer : public buffer { private: enum { buffer_size = 256 }; T data_[buffer_size]; size_t count_ = 0; static FMT_CONSTEXPR void grow(buffer& buf, size_t) { if (buf.size() != buffer_size) return; static_cast(buf).count_ += buf.size(); buf.clear(); } public: FMT_CONSTEXPR counting_buffer() : buffer(grow, data_, 0, buffer_size) {} constexpr auto count() const noexcept -> size_t { return count_ + this->size(); } }; template struct is_back_insert_iterator> : std::true_type {}; template struct has_back_insert_iterator_container_append : std::false_type {}; template struct has_back_insert_iterator_container_append< OutputIt, InputIt, void_t()) .append(std::declval(), std::declval()))>> : std::true_type {}; template struct has_back_insert_iterator_container_insert_at_end : std::false_type {}; template struct has_back_insert_iterator_container_insert_at_end< OutputIt, InputIt, void_t()) .insert(get_container(std::declval()).end(), std::declval(), std::declval()))>> : std::true_type {}; // An optimized version of std::copy with the output value type (T). template ::value&& has_back_insert_iterator_container_append< OutputIt, InputIt>::value)> FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { get_container(out).append(begin, end); return out; } template ::value && !has_back_insert_iterator_container_append< OutputIt, InputIt>::value && has_back_insert_iterator_container_insert_at_end< OutputIt, InputIt>::value)> FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { auto& c = get_container(out); c.insert(c.end(), begin, end); return out; } template ::value && (has_back_insert_iterator_container_append< OutputIt, InputIt>::value || has_back_insert_iterator_container_insert_at_end< OutputIt, InputIt>::value)))> FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { while (begin != end) *out++ = static_cast(*begin++); return out; } template FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { return copy(s.begin(), s.end(), out); } template struct is_buffer_appender : std::false_type {}; template struct is_buffer_appender< It, bool_constant< is_back_insert_iterator::value && std::is_base_of, typename It::container_type>::value>> : std::true_type {}; // Maps an output iterator to a buffer. template ::value)> auto get_buffer(OutputIt out) -> iterator_buffer { return iterator_buffer(out); } template ::value)> auto get_buffer(OutputIt out) -> buffer& { return get_container(out); } template auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { return buf.out(); } template auto get_iterator(buffer&, OutputIt out) -> OutputIt { return out; } // This type is intentionally undefined, only used for errors. template struct type_is_unformattable_for; template struct string_value { const Char* data; size_t size; auto str() const -> basic_string_view { return {data, size}; } }; template struct custom_value { using char_type = typename Context::char_type; void* value; void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); }; template struct named_arg_value { const named_arg_info* data; size_t size; }; struct custom_tag {}; #if !FMT_BUILTIN_TYPES # define FMT_BUILTIN , monostate #else # define FMT_BUILTIN #endif // 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_opt int128_value; uint128_opt 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(signed char x) : int_value(x) {} constexpr FMT_INLINE value(unsigned char x FMT_BUILTIN) : uint_value(x) {} constexpr FMT_INLINE value(signed short x) : int_value(x) {} constexpr FMT_INLINE value(unsigned short x FMT_BUILTIN) : uint_value(x) {} constexpr FMT_INLINE value(int x) : int_value(x) {} constexpr FMT_INLINE value(unsigned x FMT_BUILTIN) : uint_value(x) {} FMT_CONSTEXPR FMT_INLINE value(long x FMT_BUILTIN) : value(long_type(x)) {} FMT_CONSTEXPR FMT_INLINE value(unsigned long x FMT_BUILTIN) : value(ulong_type(x)) {} constexpr FMT_INLINE value(long long x FMT_BUILTIN) : long_long_value(x) {} constexpr FMT_INLINE value(unsigned long long x FMT_BUILTIN) : ulong_long_value(x) {} FMT_INLINE value(int128_opt x FMT_BUILTIN) : int128_value(x) {} FMT_INLINE value(uint128_opt x FMT_BUILTIN) : uint128_value(x) {} constexpr FMT_INLINE value(bool x FMT_BUILTIN) : bool_value(x) {} template constexpr FMT_INLINE value(bitint x FMT_BUILTIN) : long_long_value(x) { static_assert(N <= 64, "unsupported _BitInt"); } template constexpr FMT_INLINE value(ubitint x FMT_BUILTIN) : ulong_long_value(x) { static_assert(N <= 64, "unsupported _BitInt"); } template ::value)> constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { static_assert( std::is_same::value || std::is_same::value, "mixing character types is disallowed"); } constexpr FMT_INLINE value(float x FMT_BUILTIN) : float_value(x) {} constexpr FMT_INLINE value(double x FMT_BUILTIN) : double_value(x) {} FMT_INLINE value(long double x FMT_BUILTIN) : long_double_value(x) {} FMT_CONSTEXPR FMT_INLINE value(char_type* x FMT_BUILTIN) { string.data = x; if (is_constant_evaluated()) string.size = 0; } FMT_CONSTEXPR FMT_INLINE value(const char_type* x FMT_BUILTIN) { string.data = x; if (is_constant_evaluated()) string.size = 0; } template , FMT_ENABLE_IF(!std::is_pointer::value)> FMT_CONSTEXPR value(const T& x FMT_BUILTIN) { static_assert(std::is_same::value, "mixing character types is disallowed"); auto sv = to_string_view(x); string.data = sv.data(); string.size = sv.size(); } FMT_INLINE value(void* x FMT_BUILTIN) : pointer(x) {} FMT_INLINE value(const void* x FMT_BUILTIN) : pointer(x) {} FMT_INLINE value(volatile void* x FMT_BUILTIN) : pointer(const_cast(x)) {} FMT_INLINE value(const volatile void* x FMT_BUILTIN) : pointer(const_cast(x)) {} FMT_INLINE value(nullptr_t) : pointer(nullptr) {} template ::value || std::is_member_pointer::value)> value(const T&) { // Formatting of arbitrary pointers is disallowed. If you want to format a // pointer cast it to `void*` or `const void*`. In particular, this forbids // formatting of `[const] volatile char*` printed as bool by iostreams. static_assert(sizeof(T) == 0, "formatting of non-void pointers is disallowed"); } template ::value)> value(const T& x) : value(format_as(x)) {} template ::value)> value(const T& x) : value(formatter::format_as(x)) {} template ::value)> value(const T& named_arg) : value(named_arg.value) {} template ::value || !FMT_BUILTIN_TYPES)> FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {} FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) : named_args{args, size} {} private: template ())> FMT_CONSTEXPR value(T& x, custom_tag) { using value_type = remove_const_t; // T may overload operator& e.g. std::vector::reference in libc++. if (!is_constant_evaluated()) { custom.value = const_cast(&reinterpret_cast(x)); } else { custom.value = nullptr; #if defined(__cpp_if_constexpr) if constexpr (std::is_same*>::value) custom.value = const_cast(&x); #endif } custom.format = format_custom; } template ())> FMT_CONSTEXPR value(const T&, custom_tag) { // Cannot format an argument; to make type T formattable provide a // formatter specialization: https://fmt.dev/latest/api.html#udt. type_is_unformattable_for _; } // Formats an argument of a custom type, such as a user-defined class. template static void format_custom(void* arg, parse_context& parse_ctx, Context& ctx) { auto f = formatter(); parse_ctx.advance_to(f.parse(parse_ctx)); using qualified_type = conditional_t(), const T, T>; // format must be const for compatibility with std::format and compilation. const auto& cf = f; ctx.advance_to(cf.format(*static_cast(arg), ctx)); } }; 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 }; template struct is_output_iterator : std::false_type {}; template <> struct is_output_iterator : std::true_type {}; template struct is_output_iterator< It, T, enable_if_t&>()++), T>::value>> : std::true_type {}; template constexpr auto encode_types() -> unsigned long long { return 0; } template constexpr auto encode_types() -> unsigned long long { return static_cast(stored_type_constant::value) | (encode_types() << packed_arg_bits); } template constexpr auto make_descriptor() -> unsigned long long { return NUM_ARGS <= max_packed_args ? encode_types() : is_unpacked_bit | NUM_ARGS; } template using arg_t = conditional_t, basic_format_arg>; template struct named_arg_store { // args_[0].named_args points to named_args to avoid bloating format_args. arg_t args[1u + NUM_ARGS]; named_arg_info named_args[static_cast(NUM_NAMED_ARGS)]; template FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) : args{{named_args, NUM_NAMED_ARGS}, values...} { int arg_index = 0, named_arg_index = 0; FMT_APPLY_VARIADIC( init_named_arg(named_args, arg_index, named_arg_index, values)); } named_arg_store(named_arg_store&& rhs) { args[0] = {named_args, NUM_NAMED_ARGS}; for (size_t i = 1; i < sizeof(args) / sizeof(*args); ++i) args[i] = rhs.args[i]; for (size_t i = 0; i < NUM_NAMED_ARGS; ++i) named_args[i] = rhs.named_args[i]; } named_arg_store(const named_arg_store& rhs) = delete; auto operator=(const named_arg_store& rhs) -> named_arg_store& = delete; auto operator=(named_arg_store&& rhs) -> named_arg_store& = delete; operator const arg_t*() const { return args + 1; } }; // An array of references to arguments. It can be implicitly converted to // `basic_format_args` for passing into type-erased formatting functions // such as `vformat`. It is a plain struct to reduce binary size in debug mode. template struct format_arg_store { // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. using type = conditional_t[max_of(1, NUM_ARGS)], named_arg_store>; type args; }; // TYPE can be different from type_constant, e.g. for __float128. template struct native_formatter { private: dynamic_format_specs specs_; public: using nonlocking = void; FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE); if (const_check(TYPE == type::char_type)) check_char_specs(specs_); return end; } template FMT_CONSTEXPR void set_debug_format(bool set = true) { specs_.set_type(set ? presentation_type::debug : presentation_type::none); } FMT_PRAGMA_CLANG(diagnostic ignored "-Wundefined-inline") template FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const -> decltype(ctx.out()); }; template struct locking : bool_constant::value == type::custom_type> {}; template struct locking>::nonlocking>> : std::false_type {}; template FMT_CONSTEXPR inline auto is_locking() -> bool { return locking::value; } template FMT_CONSTEXPR inline auto is_locking() -> bool { return locking::value || is_locking(); } FMT_API void vformat_to(buffer& buf, string_view fmt, format_args args, locale_ref loc = {}); #if FMT_WIN32 FMT_API void vprint_mojibake(FILE*, string_view, format_args, bool); #else // format_args is passed by reference since it is defined later. inline void vprint_mojibake(FILE*, string_view, const format_args&, bool) {} #endif } // namespace detail // The main public API. template FMT_CONSTEXPR void parse_context::do_check_arg_id(int arg_id) { // Argument id is only checked at compile time during parsing because // formatting has its own validation. if (detail::is_constant_evaluated() && use_constexpr_cast) { auto ctx = static_cast*>(this); if (arg_id >= ctx->num_args()) report_error("argument not found"); } } template FMT_CONSTEXPR void parse_context::check_dynamic_spec(int arg_id) { using detail::compile_parse_context; if (detail::is_constant_evaluated() && use_constexpr_cast) static_cast*>(this)->check_dynamic_spec(arg_id); } FMT_BEGIN_EXPORT // An output iterator that appends to a buffer. It is used instead of // back_insert_iterator to reduce symbol sizes and avoid dependency. template class basic_appender { protected: detail::buffer* container; public: using container_type = detail::buffer; FMT_CONSTEXPR basic_appender(detail::buffer& buf) : container(&buf) {} FMT_CONSTEXPR20 auto operator=(T c) -> basic_appender& { container->push_back(c); return *this; } FMT_CONSTEXPR20 auto operator*() -> basic_appender& { return *this; } FMT_CONSTEXPR20 auto operator++() -> basic_appender& { return *this; } FMT_CONSTEXPR20 auto operator++(int) -> basic_appender { return *this; } }; // A formatting argument. Context is a template parameter for the compiled API // where output can be unbuffered. template class basic_format_arg { private: detail::value value_; detail::type type_; friend class basic_format_args; using char_type = typename Context::char_type; public: class handle { private: detail::custom_value custom_; public: explicit handle(detail::custom_value custom) : custom_(custom) {} void format(parse_context& parse_ctx, Context& ctx) const { custom_.format(custom_.value, parse_ctx, ctx); } }; constexpr basic_format_arg() : type_(detail::type::none_type) {} basic_format_arg(const detail::named_arg_info* args, size_t size) : value_(args, size) {} template basic_format_arg(T&& val) : value_(val), type_(detail::stored_type_constant::value) {} constexpr explicit operator bool() const noexcept { return type_ != detail::type::none_type; } auto type() const -> detail::type { return type_; } /** * 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`. */ template FMT_CONSTEXPR FMT_INLINE auto visit(Visitor&& vis) const -> decltype(vis(0)) { using detail::map; switch (type_) { case detail::type::none_type: break; case detail::type::int_type: return vis(value_.int_value); case detail::type::uint_type: return vis(value_.uint_value); case detail::type::long_long_type: return vis(value_.long_long_value); case detail::type::ulong_long_type: return vis(value_.ulong_long_value); case detail::type::int128_type: return vis(map(value_.int128_value)); case detail::type::uint128_type: return vis(map(value_.uint128_value)); case detail::type::bool_type: return vis(value_.bool_value); case detail::type::char_type: return vis(value_.char_value); case detail::type::float_type: return vis(value_.float_value); case detail::type::double_type: return vis(value_.double_value); case detail::type::long_double_type: return vis(value_.long_double_value); case detail::type::cstring_type: return vis(value_.string.data); case detail::type::string_type: return vis(value_.string.str()); case detail::type::pointer_type: return vis(value_.pointer); case detail::type::custom_type: return vis(handle(value_.custom)); } return vis(monostate()); } auto format_custom(const char_type* parse_begin, parse_context& parse_ctx, Context& ctx) -> bool { if (type_ != detail::type::custom_type) return false; parse_ctx.advance_to(parse_begin); value_.custom.format(value_.custom.value, parse_ctx, ctx); return true; } }; /** * 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(fmt::string_view fmt, fmt::format_args args); // OK * fmt::format_args args = fmt::make_format_args(); // Dangling reference */ template class basic_format_args { 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 basic_format_arg* args_; }; constexpr auto is_packed() const -> bool { return (desc_ & detail::is_unpacked_bit) == 0; } constexpr 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 mask = (1 << detail::packed_arg_bits) - 1; return static_cast((desc_ >> shift) & mask); } template using store = detail::format_arg_store; public: using format_arg = basic_format_arg; constexpr basic_format_args() : desc_(0), args_(nullptr) {} /// Constructs a `basic_format_args` object from `format_arg_store`. template constexpr FMT_ALWAYS_INLINE basic_format_args( const store& s) : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), values_(s.args) {} template detail::max_packed_args)> constexpr basic_format_args(const store& s) : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), args_(s.args) {} /// Constructs a `basic_format_args` object from a dynamic list of arguments. constexpr basic_format_args(const format_arg* args, int count, bool has_named = false) : desc_(detail::is_unpacked_bit | detail::to_unsigned(count) | (has_named ? +detail::has_named_args_bit : 0)), args_(args) {} /// Returns the argument with the specified id. FMT_CONSTEXPR auto get(int id) const -> format_arg { auto arg = format_arg(); if (!is_packed()) { if (id < max_size()) arg = args_[id]; return arg; } if (static_cast(id) >= detail::max_packed_args) return arg; arg.type_ = type(id); if (arg.type_ != detail::type::none_type) 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 FMT_CONSTEXPR 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); } }; // A formatting context. class context { private: appender out_; format_args args_; FMT_NO_UNIQUE_ADDRESS locale_ref loc_; public: using char_type = char; ///< The character type for the output. using iterator = appender; using format_arg = basic_format_arg; enum { builtin_types = FMT_BUILTIN_TYPES }; /// Constructs a `context` object. References to the arguments are stored /// in the object so make sure they have appropriate lifetimes. FMT_CONSTEXPR context(iterator out, format_args args, locale_ref loc = {}) : out_(out), args_(args), loc_(loc) {} context(context&&) = default; context(const context&) = delete; void operator=(const context&) = delete; FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } inline auto arg(string_view name) const -> format_arg { return args_.get(name); } FMT_CONSTEXPR auto arg_id(string_view name) const -> int { return args_.get_id(name); } auto args() const -> const format_args& { return args_; } // Returns an iterator to the beginning of the output range. FMT_CONSTEXPR auto out() const -> iterator { return out_; } // Advances the begin iterator to `it`. FMT_CONSTEXPR void advance_to(iterator) {} FMT_CONSTEXPR auto locale() const -> locale_ref { return loc_; } }; template struct runtime_format_string { basic_string_view str; }; /** * Creates a runtime format string. * * **Example**: * * // Check format string at runtime instead of compile-time. * fmt::print(fmt::runtime("{:d}"), "I am not a number"); */ inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } /// A compile-time format string. Use `format_string` in the public API to /// prevent type deduction. template struct fstring { private: static constexpr int num_static_named_args = detail::count_static_named_args(); using checker = detail::format_string_checker< char, static_cast(sizeof...(T)), num_static_named_args, num_static_named_args != detail::count_named_args()>; using arg_pack = detail::arg_pack; public: string_view str; using t = fstring; // Reports a compile-time error if S is not a valid format string for T. template FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) { using namespace detail; static_assert(count<(is_view>::value && std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); #ifdef FMT_ENFORCE_COMPILE_STRING static_assert( FMT_USE_CONSTEVAL && sizeof(s) != 0, "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); #endif } template ::value)> FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { auto sv = string_view(str); if (FMT_USE_CONSTEVAL) detail::parse_format_string(sv, checker(sv, arg_pack())); #ifdef FMT_ENFORCE_COMPILE_STRING static_assert( FMT_USE_CONSTEVAL && sizeof(s) != 0, "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); #endif } template ::value&& std::is_same::value)> FMT_ALWAYS_INLINE fstring(const S&) : str(S()) { FMT_CONSTEXPR auto sv = string_view(S()); FMT_CONSTEXPR int unused = (parse_format_string(sv, checker(sv, arg_pack())), 0); detail::ignore_unused(unused); } fstring(runtime_format_string<> fmt) : str(fmt.str) {} // Returning by reference generates better code in debug mode. FMT_ALWAYS_INLINE operator const string_view&() const { return str; } auto get() const -> string_view { return str; } }; template using format_string = typename fstring::t; template using is_formattable = bool_constant::value, int*, T>, Char>, void>::value>; #ifdef __cpp_concepts template concept formattable = is_formattable, Char>::value; #endif // A formatter specialization for natively supported types. template struct formatter::value != detail::type::custom_type>> : detail::native_formatter::value> { }; /** * Constructs an object that stores references to arguments and can be * implicitly converted to `format_args`. `Context` can be omitted in which case * it defaults to `context`. See `arg` for lifetime considerations. */ // Take arguments by lvalue references to avoid some lifetime issues, e.g. // auto args = make_format_args(std::string()); template (), unsigned long long DESC = detail::make_descriptor()> constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) -> detail::format_arg_store { // Suppress warnings for pathological types convertible to detail::value. FMT_PRAGMA_GCC(diagnostic ignored "-Wconversion") return {{args...}}; } template using vargs = detail::format_arg_store(), detail::make_descriptor()>; /** * Returns a named argument to be used in a formatting function. * It should only be used in a call to a formatting function. * * **Example**: * * fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); */ template inline auto arg(const Char* name, const T& arg) -> detail::named_arg { return {name, arg}; } /// Formats a string and writes the output to `out`. template , char>::value)> auto vformat_to(OutputIt&& out, string_view fmt, format_args args) -> remove_cvref_t { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, fmt, args, {}); return detail::get_iterator(buf, out); } /** * 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. `format_to` does not append a terminating null character. * * **Example**: * * auto out = std::vector(); * fmt::format_to(std::back_inserter(out), "{}", 42); */ template , char>::value)> FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) -> remove_cvref_t { return vformat_to(out, fmt.str, vargs{{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 traits = detail::fixed_buffer_traits; auto buf = detail::iterator_buffer(out, n); detail::vformat_to(buf, fmt, args, {}); return {buf.out(), buf.count()}; } /** * 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. `format_to_n` does not append a terminating null character. */ template ::value)> FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, T&&... args) -> format_to_n_result { return vformat_to_n(out, n, fmt.str, vargs{{args...}}); } struct format_to_result { /// Pointer to just after the last successful write in the array. char* out; /// Specifies if the output was truncated. bool truncated; FMT_CONSTEXPR operator char*() const { // Report truncation to prevent silent data loss. if (truncated) report_error("output is truncated"); return out; } }; template auto vformat_to(char (&out)[N], string_view fmt, format_args args) -> format_to_result { auto result = vformat_to_n(out, N, fmt, args); return {result.out, result.size > N}; } template FMT_INLINE auto format_to(char (&out)[N], format_string fmt, T&&... args) -> format_to_result { auto result = vformat_to_n(out, N, fmt.str, vargs{{args...}}); return {result.out, result.size > N}; } /// Returns the number of chars in the output of `format(fmt, args...)`. template FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); detail::vformat_to(buf, fmt.str, vargs{{args...}}, {}); return buf.count(); } FMT_API void vprint(string_view fmt, format_args args); FMT_API void vprint(FILE* f, string_view fmt, format_args args); FMT_API void vprintln(FILE* f, string_view fmt, format_args args); FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); /** * Formats `args` according to specifications in `fmt` and writes the output * to `stdout`. * * **Example**: * * fmt::print("The answer is {}.", 42); */ template FMT_INLINE void print(format_string fmt, T&&... args) { vargs va = {{args...}}; if (detail::const_check(!detail::use_utf8)) return detail::vprint_mojibake(stdout, fmt.str, va, false); return detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) : vprint(fmt.str, va); } /** * Formats `args` according to specifications in `fmt` and writes the * output to the file `f`. * * **Example**: * * fmt::print(stderr, "Don't {}!", "panic"); */ template FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; if (detail::const_check(!detail::use_utf8)) return detail::vprint_mojibake(f, fmt.str, va, false); return detail::is_locking() ? vprint_buffered(f, fmt.str, va) : vprint(f, fmt.str, va); } /// Formats `args` according to specifications in `fmt` and writes the output /// to the file `f` followed by a newline. template FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; return detail::const_check(detail::use_utf8) ? vprintln(f, fmt.str, va) : detail::vprint_mojibake(f, fmt.str, va, true); } /// Formats `args` according to specifications in `fmt` and writes the output /// to `stdout` followed by a newline. template FMT_INLINE void println(format_string fmt, T&&... args) { return fmt::println(stdout, fmt, static_cast(args)...); } FMT_PRAGMA_GCC(diagnostic pop) FMT_PRAGMA_CLANG(diagnostic pop) FMT_PRAGMA_GCC(pop_options) FMT_END_EXPORT FMT_END_NAMESPACE #ifdef FMT_HEADER_ONLY # include "format.h" #endif #endif // FMT_BASE_H_ pavel-odintsov-fastnetmon-394fbe0/src/fmt/compile.h000066400000000000000000000500151520703010000224210ustar00rootroot00000000000000// 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_ #ifndef FMT_MODULE # include // std::back_inserter #endif #include "format.h" FMT_BEGIN_NAMESPACE FMT_BEGIN_EXPORT // A compile-time string which is compiled into fast formatting code. class compiled_string {}; template struct is_compiled_string : std::is_base_of {}; /** * 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); */ #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) # define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string) #else # define FMT_COMPILE(s) FMT_STRING(s) #endif /** * Converts a string literal into a format string that will be parsed at * compile time and converted into efficient formatting code. Requires support * for class types in constant template parameters (a C++20 feature). * * **Example**: * * // Converts 42 into std::string using the most efficient method and no * // runtime format string processing. * using namespace fmt::literals; * std::string s = fmt::format("{}"_cf, 42); */ #if FMT_USE_NONTYPE_TEMPLATE_ARGS inline namespace literals { template constexpr auto operator""_cf() { return FMT_COMPILE(Str.data); } } // namespace literals #endif FMT_END_EXPORT namespace detail { template constexpr auto first(const T& value, const Tail&...) -> const T& { return value; } #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template struct type_list {}; // Returns a reference to the argument at index N from [first, rest...]. template constexpr auto get([[maybe_unused]] const T& first, [[maybe_unused]] const Args&... rest) -> const auto& { static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); if constexpr (N == 0) return first; else return detail::get(rest...); } # if FMT_USE_NONTYPE_TEMPLATE_ARGS template constexpr auto get_arg_index_by_name(basic_string_view name) -> int { if constexpr (is_static_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 -1; } # endif template FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { # if FMT_USE_NONTYPE_TEMPLATE_ARGS if constexpr (sizeof...(Args) > 0) return get_arg_index_by_name<0, Args...>(name); # endif (void)name; return -1; } template constexpr auto get_arg_index_by_name(basic_string_view name, type_list) -> int { 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 auto format(OutputIt out, const T&...) const -> OutputIt { return write(out, data); } }; template struct is_compiled_format> : std::true_type {}; template constexpr auto make_text(basic_string_view s, size_t pos, size_t size) -> text { return {{&s[pos], size}}; } template struct code_unit { Char value; using char_type = Char; template constexpr auto format(OutputIt out, const T&...) const -> OutputIt { *out++ = value; return out; } }; // This ensures that the argument type is convertible to `const T&`. template constexpr auto get_arg_checked(const Args&... args) -> const T& { const auto& arg = detail::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 auto format(OutputIt out, const T&... args) const -> OutputIt { const V& arg = get_arg_checked(args...); if constexpr (std::is_convertible>::value) { auto s = basic_string_view(arg); return copy(s.begin(), s.end(), out); } else { return write(out, arg); } } }; 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 auto 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) -> bool { if constexpr (is_named_arg::type>::value) { if (arg_name == arg.name) { out = write(out, arg.value); return true; } } return false; } template constexpr auto format(OutputIt out, const T&... args) const -> OutputIt { bool found = (try_format_argument(out, name, args) || ...); if (!found) { FMT_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 auto format(OutputIt out, const T&... args) const -> OutputIt { 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 auto format(OutputIt out, const T&... args) const -> OutputIt { out = lhs.format(out, args...); return rhs.format(out, args...); } }; template struct is_compiled_format> : std::true_type {}; template constexpr auto make_concat(L lhs, R rhs) -> concat { return {lhs, rhs}; } struct unknown_format {}; template constexpr auto parse_text(basic_string_view str, size_t pos) -> size_t { for (size_t size = str.size(); pos != size; ++pos) { if (str[pos] == '{' || str[pos] == '}') break; } return pos; } template constexpr auto compile_format_string(S fmt); template constexpr auto parse_tail(T head, S fmt) { if constexpr (POS != basic_string_view(fmt).size()) { constexpr auto tail = compile_format_string(fmt); 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; }; enum { manual_indexing_id = -1 }; template constexpr auto parse_specs(basic_string_view str, size_t pos, int next_arg_id) -> parse_specs_result { str.remove_prefix(pos); auto ctx = compile_parse_context(str, max_value(), nullptr, next_arg_id); auto f = formatter(); auto end = f.parse(ctx); return {f, pos + fmt::detail::to_unsigned(end - str.data()), next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; } template struct arg_id_handler { arg_id_kind kind; arg_ref arg_id; constexpr auto on_auto() -> int { FMT_ASSERT(false, "handler cannot be used with automatic indexing"); return 0; } constexpr auto on_index(int id) -> int { kind = arg_id_kind::index; arg_id = arg_ref(id); return 0; } constexpr auto on_name(basic_string_view id) -> int { kind = arg_id_kind::name; arg_id = arg_ref(id); return 0; } }; template struct parse_arg_id_result { arg_id_kind kind; 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_id_kind::none, arg_ref{}}; auto arg_id_end = parse_arg_id(begin, end, handler); return parse_arg_id_result{handler.kind, 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 fmt) { using char_type = typename S::char_type; constexpr auto str = basic_string_view(fmt); constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); if constexpr (c == '}') { return parse_tail( field::type, ARG_INDEX>(), fmt); } else if constexpr (c != ':') { FMT_THROW(format_error("expected ':'")); } else { constexpr auto result = parse_specs::type>( str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); if constexpr (result.end >= str.size() || str[result.end] != '}') { FMT_THROW(format_error("expected '}'")); return 0; } else { return parse_tail( spec_field::type, ARG_INDEX>{ result.fmt}, fmt); } } } // Compiles a non-empty format string and returns the compiled representation // or unknown_format() on unrecognized input. template constexpr auto compile_format_string(S fmt) { using char_type = typename S::char_type; constexpr auto str = basic_string_view(fmt); if constexpr (str[POS] == '{') { if constexpr (POS + 1 == str.size()) FMT_THROW(format_error("unmatched '{' in format string")); if constexpr (str[POS + 1] == '{') { return parse_tail(make_text(str, POS, 1), fmt); } 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>(fmt); } 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.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.index; return parse_replacement_field_then_tail, Args, arg_id_end_pos, arg_index, manual_indexing_id>( fmt); } else if constexpr (arg_id_result.kind == arg_id_kind::name) { constexpr auto arg_index = get_arg_index_by_name(arg_id_result.arg_id.name, Args{}); if constexpr (arg_index >= 0) { 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>(fmt); } else if constexpr (c == '}') { return parse_tail( runtime_named_field{arg_id_result.arg_id.name}, fmt); } else if constexpr (c == ':') { return unknown_format(); // no type info for specs parsing } } } } else if constexpr (str[POS] == '}') { if constexpr (POS + 1 == str.size()) FMT_THROW(format_error("unmatched '}' in format string")); return parse_tail(make_text(str, POS, 1), fmt); } else { constexpr auto end = parse_text(str, POS + 1); if constexpr (end - POS > 1) { return parse_tail(make_text(str, POS, end - POS), fmt); } else { return parse_tail(code_unit{str[POS]}, fmt); } } } template ::value)> constexpr auto compile(S fmt) { constexpr auto str = basic_string_view(fmt); if constexpr (str.size() == 0) { return detail::make_text(str, 0, 0); } else { constexpr auto result = detail::compile_format_string, 0, 0>(fmt); return result; } } #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) } // namespace detail FMT_BEGIN_EXPORT #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template ::value)> FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf, const T&... args) -> std::basic_string { auto s = std::basic_string(); cf.format(std::back_inserter(s), args...); return s; } template ::value)> constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf, const T&... args) -> OutputIt { return cf.format(out, args...); } template ::value)> FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args) -> std::basic_string { 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 fmt::format( static_cast>(S()), std::forward(args)...); } else { return fmt::format(compiled, std::forward(args)...); } } template ::value)> FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt { constexpr auto compiled = detail::compile(S()); if constexpr (std::is_same, detail::unknown_format>()) { return fmt::format_to( out, static_cast>(S()), std::forward(args)...); } else { return fmt::format_to(out, compiled, std::forward(args)...); } } #endif template ::value)> auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args) -> format_to_n_result { using traits = detail::fixed_buffer_traits; auto buf = detail::iterator_buffer(out, n); fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); return {buf.out(), buf.count()}; } template ::value)> FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); fmt::format_to(appender(buf), fmt, std::forward(args)...); return buf.count(); } template ::value)> void print(std::FILE* f, const S& fmt, T&&... args) { auto buf = memory_buffer(); fmt::format_to(appender(buf), fmt, std::forward(args)...); detail::print(f, {buf.data(), buf.size()}); } template ::value)> void print(const S& fmt, T&&... args) { print(stdout, fmt, std::forward(args)...); } template class static_format_result { private: char data[N]; public: template ::value)> explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) { *fmt::format_to(data, fmt, std::forward(args)...) = '\0'; } auto str() const -> fmt::string_view { return {data, N - 1}; } auto c_str() const -> const char* { return data; } }; /** * Formats arguments according to the format string `fmt_str` and produces * a string of the exact required size at compile time. Both the format string * and the arguments must be compile-time expressions. * * The resulting string can be accessed as a C string via `c_str()` or as * a `fmt::string_view` via `str()`. * * **Example**: * * // Produces the static string "42" at compile time. * static constexpr auto result = FMT_STATIC_FORMAT("{}", 42); * const char* s = result.c_str(); */ #define FMT_STATIC_FORMAT(fmt_str, ...) \ fmt::static_format_result< \ fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \ FMT_COMPILE(fmt_str), __VA_ARGS__) FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ pavel-odintsov-fastnetmon-394fbe0/src/fmt/core.h000066400000000000000000000002731520703010000217220ustar00rootroot00000000000000// This file is only provided for compatibility and may be removed in future // versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h // otherwise. #include "format.h" pavel-odintsov-fastnetmon-394fbe0/src/fmt/format-inl.h000066400000000000000000002361601520703010000230500ustar00rootroot00000000000000// 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_ #ifndef FMT_MODULE # include # include // errno # include # include # include #endif #if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) # include // _isatty #endif #include "format.h" #if FMT_USE_LOCALE && !defined(FMT_MODULE) # include #endif #ifndef FMT_FUNC # define FMT_FUNC #endif FMT_BEGIN_NAMESPACE #ifndef FMT_CUSTOM_ASSERT_FAIL 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); abort(); } #endif #if FMT_USE_LOCALE namespace detail { using std::locale; using std::numpunct; using std::use_facet; } // namespace detail #else namespace detail { struct locale {}; template struct numpunct { auto grouping() const -> std::string { return "\03"; } auto thousands_sep() const -> Char { return ','; } auto decimal_point() const -> Char { return '.'; } }; template Facet use_facet(locale) { return {}; } } // namespace detail #endif // FMT_USE_LOCALE template auto locale_ref::get() const -> Locale { using namespace detail; static_assert(std::is_same::value, ""); #if FMT_USE_LOCALE if (locale_) return *static_cast(locale_); #endif return locale(); } namespace detail { FMT_FUNC void format_error_code(detail::buffer& out, int error_code, string_view message) 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 = appender(out); if (message.size() <= inline_buffer_size - error_code_size) fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); FMT_ASSERT(out.size() <= inline_buffer_size, ""); } FMT_FUNC void do_report_error(format_func func, int error_code, const char* message) noexcept { memory_buffer full_message; func(full_message, error_code, message); // Don't use fwrite_all 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_all(const void* ptr, size_t count, FILE* stream) { size_t written = std::fwrite(ptr, 1, count, stream); if (written < count) FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } template FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { auto&& facet = 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 auto decimal_point_impl(locale_ref loc) -> Char { return use_facet>(loc.get()).decimal_point(); } #if FMT_USE_LOCALE FMT_FUNC auto write_loc(appender out, loc_value value, const format_specs& specs, locale_ref loc) -> bool { auto locale = loc.get(); // We cannot use the num_put facet because it may produce output in // a wrong encoding. using facet = format_facet; if (std::has_facet(locale)) return use_facet(locale).put(out, value, specs); return facet(locale).put(out, value, specs); } #endif } // namespace detail FMT_FUNC void report_error(const char* message) { #if FMT_MSC_VERSION || defined(__NVCC__) // 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) return; #endif FMT_THROW(format_error(message)); } template typename Locale::id format_facet::id; template format_facet::format_facet(Locale& loc) { auto& np = detail::use_facet>(loc); grouping_ = np.grouping(); if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep()); } #if FMT_USE_LOCALE template <> FMT_API FMT_FUNC auto format_facet::do_put( appender out, loc_value val, const format_specs& specs) const -> bool { return val.visit( detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); } #endif FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) -> std::system_error { auto ec = std::error_code(error_code, std::generic_category()); return std::system_error(ec, vformat(fmt, args)); } namespace detail { template inline auto operator==(basic_fp x, basic_fp y) -> bool { return x.f == y.f && x.e == y.e; } // Compilers should be able to optimize this into the ror instruction. FMT_INLINE auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { r &= 31; return (n >> r) | (n << (32 - r)); } FMT_INLINE auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { r &= 63; return (n >> r) | (n << (64 - r)); } // Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. namespace dragonbox { // Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { return umul128_upper64(static_cast(x) << 32, y); } // Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept -> uint128_fallback { uint64_t high = x * y.high(); uint128_fallback high_low = umul128(x, y.low()); return {high + high_low.high(), high_low.low()}; } // Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { return x * y; } // Various fast log computations. inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); return (e * 631305 - 261663) >> 21; } FMT_INLINE_VARIABLE constexpr struct div_small_pow10_infos_struct { uint32_t divisor; int shift_amount; } div_small_pow10_infos[] = {{10, 16}, {100, 16}}; // Replaces n by floor(n / pow(10, N)) returning true if and only if n is // divisible by pow(10, N). // Precondition: n <= pow(10, N + 1). template auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { // The numbers below are chosen such that: // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, // 2. nm mod 2^k < m if and only if n is divisible by d, // where m is magic_number, k is shift_amount // and d is divisor. // // Item 1 is a common technique of replacing division by a constant with // multiplication, see e.g. "Division by Invariant Integers Using // Multiplication" by Granlund and Montgomery (1994). magic_number (m) is set // to ceil(2^k/d) for large enough k. // The idea for item 2 originates from Schubfach. constexpr auto info = div_small_pow10_infos[N - 1]; FMT_ASSERT(n <= info.divisor * 10, "n is too large"); constexpr uint32_t magic_number = (1u << info.shift_amount) / info.divisor + 1; n *= magic_number; const uint32_t comparison_mask = (1u << info.shift_amount) - 1; bool result = (n & comparison_mask) < magic_number; n >>= info.shift_amount; return result; } // Computes floor(n / pow(10, N)) for small n and N. // Precondition: n <= pow(10, N + 1). template auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { constexpr auto info = div_small_pow10_infos[N - 1]; FMT_ASSERT(n <= info.divisor * 10, "n is too large"); constexpr uint32_t magic_number = (1u << info.shift_amount) / info.divisor + 1; return (n * magic_number) >> info.shift_amount; } // Computes floor(n / 10^(kappa + 1)) (float) inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { // 1374389535 = ceil(2^37/100) return static_cast((static_cast(n) * 1374389535) >> 37); } // Computes floor(n / 10^(kappa + 1)) (double) inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { // 2361183241434822607 = ceil(2^(64+7)/1000) return umul128_upper64(n, 2361183241434822607ull) >> 7; } // 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 auto get_cached_power(int k) noexcept -> uint64_t { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); static constexpr 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, 0x813f3978f8940985, 0xa18f07d736b90be6, 0xc9f2c9cd04674edf, 0xfc6f7c4045812297, 0x9dc5ada82b70b59e, 0xc5371912364ce306, 0xf684df56c3e01bc7, 0x9a130b963a6c115d, 0xc097ce7bc90715b4, 0xf0bdc21abb48db21, 0x96769950b50d88f5, 0xbc143fa4e250eb32, 0xeb194f8e1ae525fe, 0x92efd1b8d0cf37bf, 0xb7abc627050305ae, 0xe596b7b0c643c71a, 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f}; return pow10_significands[k - float_info::min_k]; } struct compute_mul_result { carrier_uint result; bool is_integer; }; struct compute_mul_parity_result { bool parity; bool is_integer; }; static auto compute_mul(carrier_uint u, const cache_entry_type& cache) noexcept -> compute_mul_result { auto r = umul96_upper64(u, cache); return {static_cast(r >> 32), static_cast(r) == 0}; } static auto compute_delta(const cache_entry_type& cache, int beta) noexcept -> uint32_t { return static_cast(cache >> (64 - 1 - beta)); } static auto compute_mul_parity(carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); auto r = umul96_lower64(two_f, cache); return {((r >> (64 - beta)) & 1) != 0, static_cast(r >> (32 - beta)) == 0}; } static auto compute_left_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( (cache - (cache >> (num_significand_bits() + 2))) >> (64 - num_significand_bits() - 1 - beta)); } static auto compute_right_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( (cache + (cache >> (num_significand_bits() + 1))) >> (64 - num_significand_bits() - 1 - beta)); } static auto compute_round_up_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (static_cast( cache >> (64 - num_significand_bits() - 2 - beta)) + 1) / 2; } }; template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint128_fallback; static auto get_cached_power(int k) noexcept -> uint128_fallback { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); static constexpr uint128_fallback 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, 0xbff8f10e7a8921a5}, {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, {0xfee50b7025c36a08, 0x02f236d04753d5b5}, {0x9f4f2726179a2245, 0x01d762422c946591}, {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, {0xacb92ed9397bf996, 0x49c2c37f07965405}, {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, {0x83c7088e1aab65db, 0x792667c6da79e0fb}, {0xa4b8cab1a1563f52, 0x577001b891185939}, {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, {0x80b05e5ac60b6178, 0x544f8158315b05b5}, {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, {0xfb5878494ace3a5f, 0x04ab48a04065c724}, {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, {0xf5746577930d6500, 0xca8f44ec7ee3647a}, {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, {0xea1575143cf97226, 0xf52d09d71a3293be}, {0x924d692ca61be758, 0x593c2626705f9c57}, {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, {0x8b865b215899f46c, 0xbd79e0d20082ee75}, {0xae67f1e9aec07187, 0xecd8590680a3aa12}, {0xda01ee641a708de9, 0xe80e6f4820cc9496}, {0x884134fe908658b2, 0x3109058d147fdcde}, {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, {0xa6539930bf6bff45, 0x84db8346b786151d}, {0xcfe87f7cef46ff16, 0xe612641865679a64}, {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, {0xa26da3999aef7749, 0xe3be5e330f38f09e}, {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, {0xc646d63501a1511d, 0xb281e1fd541501b9}, {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, {0x9ae757596946075f, 0x3375788de9b06959}, {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, {0xbd176620a501fbff, 0xb650e5a93bc3d899}, {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, {0x93ba47c980e98cdf, 0xc66f336c36b10138}, {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, {0x9043ea1ac7e41392, 0x87c89837ad68db30}, {0xb454e4a179dd1877, 0x29babe4598c311fc}, {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, {0xdc21a1171d42645d, 0x76707543f4fa1f74}, {0x899504ae72497eba, 0x6a06494a791c53a9}, {0xabfa45da0edbde69, 0x0487db9d17636893}, {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, {0xa7f26836f282b732, 0x8e6cac7768d7141f}, {0xd1ef0244af2364ff, 0x3207d795430cd927}, {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, {0xcd036837130890a1, 0x36dba887c37a8c10}, {0x802221226be55a64, 0xc2494954da2c978a}, {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, {0x9c69a97284b578d7, 0xff2a760414536efc}, {0xc38413cf25e2d70d, 0xfef5138519684abb}, {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, {0xba756174393d88df, 0x94f971119aeef9e5}, {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, {0x91abb422ccb812ee, 0xac62e055c10ab33b}, {0xb616a12b7fe617aa, 0x577b986b314d600a}, {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, {0x8e41ade9fbebc27d, 0x14588f13be847308}, {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, {0x8aec23d680043bee, 0x25de7bb9480d5855}, {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, {0xd910f7ff28069da4, 0x1b2ba1518094da05}, {0x87aa9aff79042286, 0x90fb44d2f05d0843}, {0xa99541bf57452b28, 0x353a1607ac744a54}, {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, {0x847c9b5d7c2e09b7, 0x69956135febada12}, {0xa59bc234db398c25, 0x43fab9837e699096}, {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, {0x9defbf01b061adab, 0x3a0888136afa64a8}, {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, {0xbc4665b596706114, 0x873d5d9f0dde1fef}, {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, {0x8fa475791a569d10, 0xf96e017d694487bd}, {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, {0xe070f78d3927556a, 0x85bbe253f47b1418}, {0x8c469ab843b89562, 0x93956d7478ccec8f}, {0xaf58416654a6babb, 0x387ac8d1970027b3}, {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, {0x88fcf317f22241e2, 0x441fece3bdf81f04}, {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, {0x85c7056562757456, 0xf6872d5667844e4a}, {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, {0xd106f86e69d785c7, 0xe13336d701beba53}, {0x82a45b450226b39c, 0xecc0024661173474}, {0xa34d721642b06084, 0x27f002d7f95d0191}, {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, {0xff290242c83396ce, 0x7e67047175a15272}, {0x9f79a169bd203e41, 0x0f0062c6e984d387}, {0xc75809c42c684dd1, 0x52c07b78a3e60869}, {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, {0xc2abf989935ddbfe, 0x6acff893d00ea436}, {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, {0x98165af37b2153de, 0xc3727a337a8b704b}, {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, {0xeda2ee1c7064130c, 0x1162def06f79df74}, {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, {0xb10d8e1456105dad, 0x7425a83e872c5f48}, {0xdd50f1996b947518, 0xd12f124e28f7771a}, {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, {0x8714a775e3e95c78, 0x65acfaec34810a72}, {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, {0xd31045a8341ca07c, 0x1ede48111209a051}, {0x83ea2b892091e44d, 0x934aed0aab460433}, {0xa4e4b66b68b65d60, 0xf81da84d56178540}, {0xce1de40642e3f4b9, 0x36251260ab9d668f}, {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, {0xa1075a24e4421730, 0xb24cf65b8612f820}, {0xc94930ae1d529cfc, 0xdee033f26797b628}, {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, {0xea53df5fd18d5513, 0x84c86189216dc5ee}, {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, {0xdf78e4b2bd342cf6, 0x914da9246b255417}, {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, {0xae9672aba3d0c320, 0xa184ac2473b529b2}, {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, {0x8865899617fb1871, 0x7e2fa67c7a658893}, {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, {0xd51ea6fa85785631, 0x552a74227f3ea566}, {0x8533285c936b35de, 0xd53a88958f872760}, {0xa67ff273b8460356, 0x8a892abaf368f138}, {0xd01fef10a657842c, 0x2d2b7569b0432d86}, {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, {0xcb3f2f7642717713, 0x241c70a936219a74}, {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, {0x9ec95d1463e8a506, 0xf4363804324a40ab}, {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, {0x976e41088617ca01, 0xd5be0503e085d814}, {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, {0x906a617d450187e2, 0x27fb2b80668b24c6}, {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, {0xe1a63853bbd26451, 0x5e7873f8a0396974}, {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, {0xac2820d9623bf429, 0x546345fa9fbdcd45}, {0xd732290fbacaf133, 0xa97c177947ad4096}, {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, {0xa0555e361951c366, 0xd7e105bcc3326220}, {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, {0xfa856334878fc150, 0xb14f98f6f0feb952}, {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, {0xeeea5d5004981478, 0x1858ccfce06cac75}, {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, {0xbaa718e68396cffd, 0xd30560258f54e6bb}, {0xe950df20247c83fd, 0x47c6b82ef32a206a}, {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, {0xb6472e511c81471d, 0xe0133fe4adf8e953}, {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, {0xb201833b35d63f73, 0x2cd2cc6551e513db}, {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, {0x8b112e86420f6191, 0xfb04afaf27faf783}, {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, {0xd94ad8b1c7380874, 0x18375281ae7822bd}, {0x87cec76f1c830548, 0x8f2293910d0b15b6}, {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, {0xd433179d9c8cb841, 0x5fa60692a46151ec}, {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, {0xa5c7ea73224deff3, 0x12b9b522906c0801}, {0xcf39e50feae16bef, 0xd768226b34870a01}, {0x81842f29f2cce375, 0xe6a1158300d46641}, {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, {0xc5a05277621be293, 0xc7098b7305241886}, {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, {0x9a65406d44a5c903, 0x737f74f1dc043329}, {0xc0fe908895cf3b44, 0x505f522e53053ff3}, {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, {0xbc789925624c5fe0, 0xb67d16413d132073}, {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, #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, 0x02f236d04753d5b5}, {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, {0xa6539930bf6bff45, 0x84db8346b786151d}, {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, {0xd910f7ff28069da4, 0x1b2ba1518094da05}, {0xaf58416654a6babb, 0x387ac8d1970027b3}, {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, {0xf13e34aabb430a15, 0x647726b9e7c68ff0} #endif }; #if FMT_USE_FULL_CACHE_DRAGONBOX return pow10_significands[k - float_info::min_k]; #else static constexpr 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 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_fallback 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_fallback recovered_cache = umul128(base_cache.high(), pow5); uint128_fallback middle_low = umul128(base_cache.low(), 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_fallback{(recovered_cache.low() >> alpha) | high_to_middle, ((middle_low.low() >> alpha) | middle_to_low)}; FMT_ASSERT(recovered_cache.low() + 1 != 0, ""); return {recovered_cache.high(), recovered_cache.low() + 1}; #endif } struct compute_mul_result { carrier_uint result; bool is_integer; }; struct compute_mul_parity_result { bool parity; bool is_integer; }; static auto compute_mul(carrier_uint u, const cache_entry_type& cache) noexcept -> compute_mul_result { auto r = umul192_upper128(u, cache); return {r.high(), r.low() == 0}; } static auto compute_delta(const cache_entry_type& cache, int beta) noexcept -> uint32_t { return static_cast(cache.high() >> (64 - 1 - beta)); } static auto compute_mul_parity(carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); auto r = umul192_lower128(two_f, cache); return {((r.high() >> (64 - beta)) & 1) != 0, ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; } static auto compute_left_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() - (cache.high() >> (num_significand_bits() + 2))) >> (64 - num_significand_bits() - 1 - beta); } static auto compute_right_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() + (cache.high() >> (num_significand_bits() + 1))) >> (64 - num_significand_bits() - 1 - beta); } static auto compute_round_up_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + 1) / 2; } }; FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { return cache_accessor::get_cached_power(k); } // Various integer checks template auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { const int case_shorter_interval_left_endpoint_lower_threshold = 2; const int case_shorter_interval_left_endpoint_upper_threshold = 3; return exponent >= case_shorter_interval_left_endpoint_lower_threshold && exponent <= case_shorter_interval_left_endpoint_upper_threshold; } // Remove trailing zeros from n and return the number of zeros removed (float). FMT_INLINE auto remove_trailing_zeros(uint32_t& n, int s = 0) noexcept -> int { FMT_ASSERT(n != 0, ""); // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. constexpr uint32_t mod_inv_5 = 0xcccccccd; constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 while (true) { auto q = rotr(n * mod_inv_25, 2); if (q > max_value() / 100) break; n = q; s += 2; } auto q = rotr(n * mod_inv_5, 1); if (q <= max_value() / 10) { n = q; s |= 1; } return s; } // Removes trailing zeros and returns the number of zeros removed (double). FMT_INLINE auto remove_trailing_zeros(uint64_t& n) noexcept -> int { FMT_ASSERT(n != 0, ""); // Is n is divisible by 10^8? constexpr uint32_t ten_pow_8 = 100000000u; if ((n % ten_pow_8) == 0) { // If yes, work with the quotient... auto n32 = static_cast(n / ten_pow_8); // ... and use the 32 bit variant of the function int num_zeros = remove_trailing_zeros(n32, 8); n = n32; return num_zeros; } // If n is not divisible by 10^8, work with n itself. constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5 int s = 0; while (true) { auto q = rotr(n * mod_inv_25, 2); if (q > max_value() / 100) break; n = q; s += 2; } auto q = rotr(n * mod_inv_5, 1); if (q <= max_value() / 10) { n = q; s |= 1; } return s; } // The main algorithm for shorter interval case template FMT_INLINE auto shorter_interval_case(int exponent) noexcept -> decimal_fp { decimal_fp ret_value; // Compute k and beta const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); const int beta = 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); auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( cache, beta); // 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); 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 auto to_decimal(T x) noexcept -> decimal_fp { // 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) << num_significand_bits()) - 1; carrier_uint significand = (br & significand_mask); int exponent = static_cast((br & exponent_mask()) >> num_significand_bits()); if (exponent != 0) { // Check if normal. exponent -= exponent_bias() + num_significand_bits(); // Shorter interval case; proceed like Schubfach. // In fact, when exponent == 1 and significand == 0, the interval is // regular. However, it can be shown that the end-results are anyway same. if (significand == 0) return shorter_interval_case(exponent); significand |= (static_cast(1) << num_significand_bits()); } else { // Subnormal case; the interval is always regular. if (significand == 0) return {0, 0}; exponent = std::numeric_limits::min_exponent - num_significand_bits() - 1; } 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 = 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); const carrier_uint two_fc = significand << 1; // For the case of binary32, the result of integer check is not correct for // 29711844 * 2^-82 // = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18 // and 29711844 * 2^-81 // = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17, // and they are the unique counterexamples. However, since 29711844 is even, // this does not cause any problem for the endpoints calculations; it can only // cause a problem when we need to perform integer check for the center. // Fortunately, with these inputs, that branch is never executed, so we are // fine. const typename cache_accessor::compute_mul_result z_mul = cache_accessor::compute_mul((two_fc | 1) << beta, 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(z_mul.result); uint32_t r = static_cast(z_mul.result - float_info::big_divisor * ret_value.significand); if (r < deltai) { // Exclude the right endpoint if necessary. if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) { --ret_value.significand; r = float_info::big_divisor; goto small_divisor_case_label; } } else if (r > deltai) { goto small_divisor_case_label; } else { // r == deltai; compare fractional parts. const typename cache_accessor::compute_mul_parity_result x_mul = cache_accessor::compute_mul_parity(two_fc - 1, cache, beta); if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint))) 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; uint32_t dist = r - (deltai / 2) + (float_info::small_divisor / 2); const bool approx_y_parity = ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; // Is dist divisible by 10^kappa? const bool divisible_by_small_divisor = check_divisibility_and_divide_by_pow10::kappa>(dist); // Add dist / 10^kappa to the significand. ret_value.significand += dist; if (!divisible_by_small_divisor) return ret_value; // 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. const auto y_mul = cache_accessor::compute_mul_parity(two_fc, cache, beta); // If z^(f) >= epsilon^(f), we might have a tie when z^(f) == epsilon^(f), // or equivalently, when y is an integer. if (y_mul.parity != approx_y_parity) --ret_value.significand; else if (y_mul.is_integer & (ret_value.significand % 2 != 0)) --ret_value.significand; return ret_value; } } // namespace dragonbox } // namespace detail template <> struct formatter { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> format_parse_context::iterator { return ctx.begin(); } auto format(const detail::bigint& n, format_context& ctx) const -> format_context::iterator { 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 = fmt::format_to(out, FMT_STRING("{:x}"), value); first = false; continue; } out = fmt::format_to(out, FMT_STRING("{:08x}"), value); } if (n.exp_ > 0) out = fmt::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, string_view) { if (cp == invalid_code_point) 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))); } return true; }); buffer_.push_back(0); } FMT_FUNC void format_system_error(detail::buffer& out, int error_code, const char* message) noexcept { FMT_TRY { auto ec = std::error_code(error_code, std::generic_category()); detail::write(appender(out), std::system_error(ec, message).what()); return; } FMT_CATCH(...) {} format_error_code(out, error_code, message); } FMT_FUNC void report_system_error(int error_code, const char* message) noexcept { do_report_error(format_system_error, error_code, message); } FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { // 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); } namespace detail { FMT_FUNC void vformat_to(buffer& buf, string_view fmt, format_args args, locale_ref loc) { auto out = appender(buf); if (fmt.size() == 2 && equal2(fmt.data(), "{}")) return args.get(0).visit(default_arg_formatter{out}); parse_format_string(fmt, format_handler<>{parse_context<>(fmt), {out, args, loc}}); } template struct span { T* data; size_t size; }; template auto flockfile(F* f) -> decltype(_lock_file(f)) { _lock_file(f); } template auto funlockfile(F* f) -> decltype(_unlock_file(f)) { _unlock_file(f); } #ifndef getc_unlocked template auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) { return _fgetc_nolock(f); } #endif template struct has_flockfile : std::false_type {}; template struct has_flockfile()))>> : std::true_type {}; // A FILE wrapper. F is FILE defined as a template parameter to make system API // detection work. template class file_base { public: F* file_; public: file_base(F* file) : file_(file) {} operator F*() const { return file_; } // Reads a code unit from the stream. auto get() -> int { int result = getc_unlocked(file_); if (result == EOF && ferror(file_) != 0) FMT_THROW(system_error(errno, FMT_STRING("getc failed"))); return result; } // Puts the code unit back into the stream buffer. void unget(char c) { if (ungetc(c, file_) == EOF) FMT_THROW(system_error(errno, FMT_STRING("ungetc failed"))); } void flush() { fflush(this->file_); } }; // A FILE wrapper for glibc. template class glibc_file : public file_base { private: enum { line_buffered = 0x200, // _IO_LINE_BUF unbuffered = 2 // _IO_UNBUFFERED }; public: using file_base::file_base; auto is_buffered() const -> bool { return (this->file_->_flags & unbuffered) == 0; } void init_buffer() { if (this->file_->_IO_write_ptr < this->file_->_IO_write_end) return; // Force buffer initialization by placing and removing a char in a buffer. putc_unlocked(0, this->file_); --this->file_->_IO_write_ptr; } // Returns the file's read buffer. auto get_read_buffer() const -> span { auto ptr = this->file_->_IO_read_ptr; return {ptr, to_unsigned(this->file_->_IO_read_end - ptr)}; } // Returns the file's write buffer. auto get_write_buffer() const -> span { auto ptr = this->file_->_IO_write_ptr; return {ptr, to_unsigned(this->file_->_IO_buf_end - ptr)}; } void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } auto needs_flush() const -> bool { if ((this->file_->_flags & line_buffered) == 0) return false; char* end = this->file_->_IO_write_end; auto size = max_of(this->file_->_IO_write_ptr - end, 0); return memchr(end, '\n', static_cast(size)); } void flush() { fflush_unlocked(this->file_); } }; // A FILE wrapper for Apple's libc. template class apple_file : public file_base { private: enum { line_buffered = 1, // __SNBF unbuffered = 2 // __SLBF }; public: using file_base::file_base; auto is_buffered() const -> bool { return (this->file_->_flags & unbuffered) == 0; } void init_buffer() { if (this->file_->_p) return; // Force buffer initialization by placing and removing a char in a buffer. if (!FMT_CLANG_ANALYZER) putc_unlocked(0, this->file_); --this->file_->_p; ++this->file_->_w; } auto get_read_buffer() const -> span { return {reinterpret_cast(this->file_->_p), to_unsigned(this->file_->_r)}; } auto get_write_buffer() const -> span { return {reinterpret_cast(this->file_->_p), to_unsigned(this->file_->_bf._base + this->file_->_bf._size - this->file_->_p)}; } void advance_write_buffer(size_t size) { this->file_->_p += size; this->file_->_w -= size; } auto needs_flush() const -> bool { if ((this->file_->_flags & line_buffered) == 0) return false; return memchr(this->file_->_p + this->file_->_w, '\n', to_unsigned(-this->file_->_w)); } }; // A fallback FILE wrapper. template class fallback_file : public file_base { private: char next_; // The next unconsumed character in the buffer. bool has_next_ = false; public: using file_base::file_base; auto is_buffered() const -> bool { return false; } auto needs_flush() const -> bool { return false; } void init_buffer() {} auto get_read_buffer() const -> span { return {&next_, has_next_ ? 1u : 0u}; } auto get_write_buffer() const -> span { return {nullptr, 0}; } void advance_write_buffer(size_t) {} auto get() -> int { has_next_ = false; return file_base::get(); } void unget(char c) { file_base::unget(c); next_ = c; has_next_ = true; } }; #ifndef FMT_USE_FALLBACK_FILE # define FMT_USE_FALLBACK_FILE 0 #endif template auto get_file(F* f, int) -> apple_file { return f; } template inline auto get_file(F* f, int) -> glibc_file { return f; } inline auto get_file(FILE* f, ...) -> fallback_file { return f; } using file_ref = decltype(get_file(static_cast(nullptr), 0)); template class file_print_buffer : public buffer { public: explicit file_print_buffer(F*) : buffer(nullptr, size_t()) {} }; template class file_print_buffer::value>> : public buffer { private: file_ref file_; static void grow(buffer& base, size_t) { auto& self = static_cast(base); self.file_.advance_write_buffer(self.size()); if (self.file_.get_write_buffer().size == 0) self.file_.flush(); auto buf = self.file_.get_write_buffer(); FMT_ASSERT(buf.size > 0, ""); self.set(buf.data, buf.size); self.clear(); } public: explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) { flockfile(f); file_.init_buffer(); auto buf = file_.get_write_buffer(); set(buf.data, buf.size); } ~file_print_buffer() { file_.advance_write_buffer(size()); bool flush = file_.needs_flush(); F* f = file_; // Make funlockfile depend on the template parameter F funlockfile(f); // for the system API detection to work. if (flush) fflush(file_); } }; #if !defined(_WIN32) || defined(FMT_USE_WRITE_CONSOLE) FMT_FUNC auto write_console(int, string_view) -> bool { return false; } #else using dword = conditional_t; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // void*, const void*, dword, dword*, void*); FMT_FUNC bool write_console(int fd, string_view text) { auto u16 = utf8_to_utf16(text); return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), static_cast(u16.size()), nullptr, nullptr) != 0; } #endif #ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args, bool newline) { auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); if (newline) buffer.push_back('\n'); fwrite_all(buffer.data(), buffer.size(), f); } #endif FMT_FUNC void print(std::FILE* f, string_view text) { #if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) int fd = _fileno(f); if (_isatty(fd)) { std::fflush(f); if (write_console(fd, text)) return; } #endif fwrite_all(text.data(), text.size(), f); } } // namespace detail FMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) { auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); detail::print(f, {buffer.data(), buffer.size()}); } FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { if (!detail::file_ref(f).is_buffered() || !detail::has_flockfile<>()) return vprint_buffered(f, fmt, args); auto&& buffer = detail::file_print_buffer<>(f); return detail::vformat_to(buffer, fmt, args); } FMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) { auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); buffer.push_back('\n'); detail::print(f, {buffer.data(), buffer.size()}); } FMT_FUNC void vprint(string_view fmt, format_args args) { vprint(stdout, fmt, args); } namespace detail { struct singleton { unsigned char upper; unsigned char lower_count; }; inline auto is_printable(uint16_t x, const singleton* singletons, size_t singletons_size, const unsigned char* singleton_lowers, const unsigned char* normal, size_t normal_size) -> bool { auto upper = x >> 8; auto lower_start = 0; for (size_t i = 0; i < singletons_size; ++i) { auto s = singletons[i]; auto lower_end = lower_start + s.lower_count; if (upper < s.upper) break; if (upper == s.upper) { for (auto j = lower_start; j < lower_end; ++j) { if (singleton_lowers[j] == (x & 0xff)) return false; } } lower_start = lower_end; } auto xsigned = static_cast(x); auto current = true; for (size_t i = 0; i < normal_size; ++i) { auto v = static_cast(normal[i]); auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v; xsigned -= len; if (xsigned < 0) break; current = !current; } return current; } // This code is generated by support/printable.py. FMT_FUNC auto is_printable(uint32_t cp) -> bool { static constexpr singleton singletons0[] = { {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8}, {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13}, {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5}, {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22}, {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3}, {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8}, {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9}, }; static constexpr unsigned char singletons0_lower[] = { 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d, 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e, 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16, 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0, 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, 0xfe, 0xff, }; static constexpr singleton singletons1[] = { {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2}, {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5}, {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5}, {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2}, {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5}, {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2}, {0xfa, 2}, {0xfb, 1}, }; static constexpr unsigned char singletons1_lower[] = { 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07, 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b, 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27, 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6, 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93, }; static constexpr unsigned char normal0[] = { 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0, 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a, 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15, 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c, 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c, 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6, 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03, 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80, 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, }; static constexpr unsigned char normal1[] = { 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09, 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08, 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e, 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41, 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04, 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03, 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1, 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a, 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09, 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89, 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6, 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09, 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50, 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05, 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07, 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e, 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07, 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06, }; auto lower = static_cast(cp); if (cp < 0x10000) { return is_printable(lower, singletons0, sizeof(singletons0) / sizeof(*singletons0), singletons0_lower, normal0, sizeof(normal0)); } if (cp < 0x20000) { return is_printable(lower, singletons1, sizeof(singletons1) / sizeof(*singletons1), singletons1_lower, normal1, sizeof(normal1)); } if (0x2a6de <= cp && cp < 0x2a700) return false; if (0x2b735 <= cp && cp < 0x2b740) return false; if (0x2b81e <= cp && cp < 0x2b820) return false; if (0x2cea2 <= cp && cp < 0x2ceb0) return false; if (0x2ebe1 <= cp && cp < 0x2f800) return false; if (0x2fa1e <= cp && cp < 0x30000) return false; if (0x3134b <= cp && cp < 0xe0100) return false; if (0xe01f0 <= cp && cp < 0x110000) return false; return cp < 0x110000; } } // namespace detail FMT_END_NAMESPACE #endif // FMT_FORMAT_INL_H_ pavel-odintsov-fastnetmon-394fbe0/src/fmt/format.h000066400000000000000000004755521520703010000223020ustar00rootroot00000000000000/* 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_ #ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES # define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES # define FMT_REMOVE_TRANSITIVE_INCLUDES #endif #include "base.h" // libc++ supports string_view in pre-c++17. #if FMT_HAS_INCLUDE() && \ (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) # define FMT_USE_STRING_VIEW #endif #ifndef FMT_MODULE # include // malloc, free # include // std::signbit # include // std::byte # include // uint32_t # include // std::memcpy # include // std::numeric_limits # include // std::bad_alloc # if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) // Workaround for pre gcc 5 libstdc++. # include // std::allocator_traits # endif # include // std::runtime_error # include // std::string # include // std::system_error // Check FMT_CPLUSPLUS to avoid a warning in MSVC. # if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L # include // std::bit_cast # endif # if defined(FMT_USE_STRING_VIEW) # include # endif # if FMT_MSC_VERSION # include // _BitScanReverse[64], _umul128 # endif #endif // FMT_MODULE #if defined(FMT_USE_NONTYPE_TEMPLATE_ARGS) // Use the provided definition. #elif defined(__NVCOMPILER) # define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 #elif FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L # define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 #elif defined(__cpp_nontype_template_args) && \ __cpp_nontype_template_args >= 201911L # define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 #elif FMT_CLANG_VERSION >= 1200 && FMT_CPLUSPLUS >= 202002L # define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 #else # define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 #endif #if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L # define FMT_INLINE_VARIABLE inline #else # define FMT_INLINE_VARIABLE #endif // Check if RTTI is disabled. #ifdef FMT_USE_RTTI // Use the provided definition. #elif defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || defined(_CPPRTTI) || \ defined(__INTEL_RTTI__) || defined(__RTTI) // __RTTI is for EDG compilers. _CPPRTTI is for MSVC. # define FMT_USE_RTTI 1 #else # define FMT_USE_RTTI 0 #endif // Visibility when compiled as a shared library/object. #if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) # define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value) #else # define FMT_SO_VISIBILITY(value) #endif #if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_NOINLINE __attribute__((noinline)) #else # define FMT_NOINLINE #endif #ifdef FMT_DEPRECATED // Use the provided definition. #elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) # define FMT_DEPRECATED [[deprecated]] #else # define FMT_DEPRECATED /* deprecated */ #endif // Detect constexpr std::string. #if !FMT_USE_CONSTEVAL # define FMT_USE_CONSTEXPR_STRING 0 #elif defined(__cpp_lib_constexpr_string) && \ __cpp_lib_constexpr_string >= 201907L # if FMT_CLANG_VERSION && FMT_GLIBCXX_RELEASE // clang + libstdc++ are able to work only starting with gcc13.3 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113294 # if FMT_GLIBCXX_RELEASE < 13 # define FMT_USE_CONSTEXPR_STRING 0 # elif FMT_GLIBCXX_RELEASE == 13 && __GLIBCXX__ < 20240521 # define FMT_USE_CONSTEXPR_STRING 0 # else # define FMT_USE_CONSTEXPR_STRING 1 # endif # else # define FMT_USE_CONSTEXPR_STRING 1 # endif #else # define FMT_USE_CONSTEXPR_STRING 0 #endif #if FMT_USE_CONSTEXPR_STRING # define FMT_CONSTEXPR_STRING constexpr #else # define FMT_CONSTEXPR_STRING #endif // GCC 4.9 doesn't support qualified names in specializations. namespace std { template struct iterator_traits> { using iterator_category = output_iterator_tag; using value_type = T; using difference_type = decltype(static_cast(nullptr) - static_cast(nullptr)); using pointer = void; using reference = void; }; } // namespace std #ifdef FMT_THROW // Use the provided definition. #elif FMT_USE_EXCEPTIONS # define FMT_THROW(x) throw x #else # define FMT_THROW(x) ::fmt::assert_fail(__FILE__, __LINE__, (x).what()) #endif #ifdef __clang_analyzer__ # define FMT_CLANG_ANALYZER 1 #else # define FMT_CLANG_ANALYZER 0 #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 FMT_BEGIN_NAMESPACE template struct is_contiguous> : std::true_type {}; namespace detail { // __builtin_clz is broken in clang with Microsoft codegen: // https://github.com/fmtlib/fmt/issues/519. #if !FMT_MSC_VERSION # if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION # define FMT_BUILTIN_CLZ(n) __builtin_clz(n) # endif # if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION # define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) # endif #endif // Some compilers masquerade as both MSVC and GCC but 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_VERSION && !defined(FMT_BUILTIN_CLZLL) // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. # ifndef __clang__ # pragma intrinsic(_BitScanReverse) # ifdef _WIN64 # pragma intrinsic(_BitScanReverse64) # endif # endif inline auto clz(uint32_t x) -> int { FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; _BitScanReverse(&r, x); return 31 ^ static_cast(r); } # define FMT_BUILTIN_CLZ(n) detail::clz(n) inline auto clzll(uint64_t x) -> int { FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; # ifdef _WIN64 _BitScanReverse64(&r, x); # else // Scan the high 32 bits. if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ static_cast(r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif return 63 ^ static_cast(r); } # define FMT_BUILTIN_CLZLL(n) detail::clzll(n) #endif // FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { ignore_unused(condition); #ifdef FMT_FUZZ if (condition) throw std::runtime_error("fuzzing limit reached"); #endif } #if defined(FMT_USE_STRING_VIEW) template using std_string_view = std::basic_string_view; #else template struct std_string_view { operator basic_string_view() const; }; #endif template struct string_literal { static constexpr Char value[sizeof...(C)] = {C...}; constexpr operator basic_string_view() const { return {value, sizeof...(C)}; } }; #if FMT_CPLUSPLUS < 201703L template constexpr Char string_literal::value[sizeof...(C)]; #endif // Implementation of std::bit_cast for pre-C++20. template FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { #ifdef __cpp_lib_bit_cast if (is_constant_evaluated()) return std::bit_cast(from); #endif auto to = To(); // The cast suppresses a bogus -Wclass-memaccess on GCC. std::memcpy(static_cast(&to), &from, sizeof(to)); return to; } inline auto is_big_endian() -> bool { #ifdef _WIN32 return false; #elif defined(__BIG_ENDIAN__) return true; #elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; #else struct bytes { char data[sizeof(int)]; }; return bit_cast(1).data[0] == 0; #endif } class uint128_fallback { private: uint64_t lo_, hi_; public: constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} constexpr auto high() const noexcept -> uint64_t { return hi_; } constexpr auto low() const noexcept -> uint64_t { return lo_; } template ::value)> constexpr explicit operator T() const { return static_cast(lo_); } friend constexpr auto operator==(const uint128_fallback& lhs, const uint128_fallback& rhs) -> bool { return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; } friend constexpr auto operator!=(const uint128_fallback& lhs, const uint128_fallback& rhs) -> bool { return !(lhs == rhs); } friend constexpr auto operator>(const uint128_fallback& lhs, const uint128_fallback& rhs) -> bool { return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; } friend constexpr auto operator|(const uint128_fallback& lhs, const uint128_fallback& rhs) -> uint128_fallback { return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; } friend constexpr auto operator&(const uint128_fallback& lhs, const uint128_fallback& rhs) -> uint128_fallback { return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; } friend constexpr auto operator~(const uint128_fallback& n) -> uint128_fallback { return {~n.hi_, ~n.lo_}; } friend FMT_CONSTEXPR auto operator+(const uint128_fallback& lhs, const uint128_fallback& rhs) -> uint128_fallback { auto result = uint128_fallback(lhs); result += rhs; return result; } friend FMT_CONSTEXPR auto operator*(const uint128_fallback& lhs, uint32_t rhs) -> uint128_fallback { FMT_ASSERT(lhs.hi_ == 0, ""); uint64_t hi = (lhs.lo_ >> 32) * rhs; uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; uint64_t new_lo = (hi << 32) + lo; return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; } friend constexpr auto operator-(const uint128_fallback& lhs, uint64_t rhs) -> uint128_fallback { return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; } FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { if (shift == 64) return {0, hi_}; if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; } FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { if (shift == 64) return {lo_, 0}; if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; } FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { return *this = *this >> shift; } FMT_CONSTEXPR void operator+=(uint128_fallback n) { uint64_t new_lo = lo_ + n.lo_; uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); FMT_ASSERT(new_hi >= hi_, ""); lo_ = new_lo; hi_ = new_hi; } FMT_CONSTEXPR void operator&=(uint128_fallback n) { lo_ &= n.lo_; hi_ &= n.hi_; } FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { if (is_constant_evaluated()) { lo_ += n; hi_ += (lo_ < n ? 1 : 0); return *this; } #if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) unsigned long long carry; lo_ = __builtin_addcll(lo_, n, 0, &carry); hi_ += carry; #elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) unsigned long long result; auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); lo_ = result; hi_ += carry; #elif defined(_MSC_VER) && defined(_M_X64) auto carry = _addcarry_u64(0, lo_, n, &lo_); _addcarry_u64(carry, hi_, 0, &hi_); #else lo_ += n; hi_ += (lo_ < n ? 1 : 0); #endif return *this; } }; using uint128_t = conditional_t; #ifdef UINTPTR_MAX using uintptr_t = ::uintptr_t; #else using uintptr_t = uint128_t; #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 128; } // A heterogeneous bit_cast used for converting 96-bit long double to uint128_t // and 128-bit pointers to uint128_fallback. template sizeof(From))> inline auto bit_cast(const From& from) -> To { constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned short)); struct data_t { unsigned short value[static_cast(size)]; } data = bit_cast(from); auto result = To(); if (const_check(is_big_endian())) { for (int i = 0; i < size; ++i) result = (result << num_bits()) | data.value[i]; } else { for (int i = size - 1; i >= 0; --i) result = (result << num_bits()) | data.value[i]; } return result; } template FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { int lz = 0; constexpr UInt msb_mask = static_cast(1) << (num_bits() - 1); for (; (n & msb_mask) == 0; n <<= 1) lz++; return lz; } FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { #ifdef FMT_BUILTIN_CLZ if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); #endif return countl_zero_fallback(n); } FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { #ifdef FMT_BUILTIN_CLZLL if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); #endif return countl_zero_fallback(n); } FMT_INLINE void assume(bool condition) { (void)condition; #if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION __builtin_assume(condition); #elif FMT_GCC_VERSION if (!condition) __builtin_unreachable(); #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&& is_contiguous::value)> #if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) #endif FMT_CONSTEXPR20 inline auto reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { auto& c = get_container(it); size_t size = c.size(); c.resize(size + n); return &c[size]; } template FMT_CONSTEXPR20 inline auto reserve(basic_appender it, size_t n) -> basic_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 FMT_CONSTEXPR auto to_pointer(T*& ptr, size_t n) -> T* { T* begin = ptr; ptr += n; return begin; } template FMT_CONSTEXPR20 auto to_pointer(basic_appender it, size_t n) -> T* { buffer& buf = get_container(it); buf.try_reserve(buf.size() + n); auto size = buf.size(); if (buf.capacity() < size + n) return nullptr; buf.try_resize(size + n); return buf.data() + size; } template ::value&& is_contiguous::value)> inline auto base_iterator(OutputIt it, typename OutputIt::container_type::value_type*) -> OutputIt { 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); static_assert(sizeof(T) == 1, "sizeof(T) must be 1 to use char for initialization"); std::memset(out, value, to_unsigned(count)); return out + count; } template FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, OutputIt out) -> OutputIt { return copy(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 int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; constexpr uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; constexpr int shiftc[] = {0, 18, 12, 6, 0}; constexpr int shifte[] = {0, 6, 4, 2, 0}; int len = "\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" [static_cast(*s) >> 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. const char* next = s + len + !len; using uchar = unsigned char; // Assume a four-byte character and load four bytes. Unused bits are // shifted out. *c = uint32_t(uchar(s[0]) & masks[len]) << 18; *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; *c >>= shiftc[len]; // Accumulate the various error conditions. *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; } constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); // Invokes f(cp, sv) for every code point cp in s with sv being the string view // corresponding to the code point. cp is invalid_code_point on error. template FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { auto decode = [f](const char* buf_ptr, const char* ptr) { auto cp = uint32_t(); auto error = 0; auto end = utf8_decode(buf_ptr, &cp, &error); bool result = f(error ? invalid_code_point : cp, string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); return result ? (error ? buf_ptr + 1 : end) : nullptr; }; 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, p); if (!p) return; } } auto num_chars_left = to_unsigned(s.data() + s.size() - p); if (num_chars_left == 0) return; // Suppress bogus -Wstringop-overflow. if (FMT_GCC_VERSION) num_chars_left &= 3; char buf[2 * block_size - 1] = {}; copy(p, p + num_chars_left, buf); const char* buf_ptr = buf; do { auto end = decode(buf_ptr, p); if (!end) return; p += end - buf_ptr; buf_ptr = end; } while (buf_ptr < buf + num_chars_left); } FMT_CONSTEXPR inline auto display_width_of(uint32_t cp) noexcept -> size_t { return to_unsigned( 1 + (cp >= 0x1100 && (cp <= 0x115f || // Hangul Jamo init. consonants cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE: (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)))); } 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>; template using is_integer = bool_constant::value && !std::is_same::value && !std::is_same::value && !std::is_same::value>; #if defined(FMT_USE_FLOAT128) // Use the provided definition. #elif FMT_CLANG_VERSION >= 309 && FMT_HAS_INCLUDE() # define FMT_USE_FLOAT128 1 #elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ !defined(__STRICT_ANSI__) # define FMT_USE_FLOAT128 1 #else # define FMT_USE_FLOAT128 0 #endif #if FMT_USE_FLOAT128 using float128 = __float128; #else struct float128 {}; #endif template using is_float128 = std::is_same; template struct is_floating_point : std::is_floating_point {}; template <> struct is_floating_point : std::true_type {}; template ::value> struct is_fast_float : bool_constant::is_iec559 && sizeof(T) <= sizeof(double)> {}; template struct is_fast_float : std::false_type {}; template using fast_float_t = conditional_t; template using is_double_double = bool_constant::digits == 106>; #ifndef FMT_USE_FULL_CACHE_DRAGONBOX # define FMT_USE_FULL_CACHE_DRAGONBOX 0 #endif // An allocator that uses malloc/free to allow removing dependency on the C++ // standard libary runtime. std::decay is used for back_inserter to be found by // ADL when applied to memory_buffer. template struct allocator : private std::decay { using value_type = T; auto allocate(size_t n) -> T* { FMT_ASSERT(n <= max_value() / sizeof(T), ""); T* p = static_cast(malloc(n * sizeof(T))); if (!p) FMT_THROW(std::bad_alloc()); return p; } void deallocate(T* p, size_t) { free(p); } constexpr friend auto operator==(allocator, allocator) noexcept -> bool { return true; // All instances of this allocator are equivalent. } constexpr friend auto operator!=(allocator, allocator) noexcept -> bool { return false; } }; template FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) -> decltype(f.set_debug_format(set)) { f.set_debug_format(set); } template FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} } // namespace detail FMT_BEGIN_EXPORT // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. enum { inline_buffer_size = 500 }; /** * A dynamically growing memory buffer for trivially copyable/constructible * types with the first `SIZE` elements stored in the object itself. Most * commonly used via the `memory_buffer` alias for `char`. * * **Example**: * * auto out = fmt::memory_buffer(); * fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); * * This will append "The answer is 42." to `out`. The buffer content can be * converted to `std::string` with `to_string(out)`. */ template > class basic_memory_buffer : public detail::buffer { private: T store_[SIZE]; // Don't inherit from Allocator to avoid generating type_info for it. FMT_NO_UNIQUE_ADDRESS Allocator alloc_; // Deallocate memory allocated by the buffer. FMT_CONSTEXPR20 void deallocate() { T* data = this->data(); if (data != store_) alloc_.deallocate(data, this->capacity()); } static FMT_CONSTEXPR20 void grow(detail::buffer& buf, size_t size) { detail::abort_fuzzing_if(size > 5000); auto& self = static_cast(buf); const size_t max_size = std::allocator_traits::max_size(self.alloc_); size_t old_capacity = buf.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 = max_of(size, max_size); T* old_data = buf.data(); T* new_data = self.alloc_.allocate(new_capacity); // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). detail::assume(buf.size() <= new_capacity); // The following code doesn't throw, so the raw pointer above doesn't leak. memcpy(new_data, old_data, buf.size() * sizeof(T)); self.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 != self.store_) self.alloc_.deallocate(old_data, old_capacity); } public: using value_type = T; using const_reference = const T&; FMT_CONSTEXPR explicit basic_memory_buffer( const Allocator& alloc = Allocator()) : detail::buffer(grow), alloc_(alloc) { this->set(store_, SIZE); if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); } FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } private: template :: propagate_on_container_move_assignment::value)> FMT_CONSTEXPR20 auto move_alloc(basic_memory_buffer& other) -> bool { alloc_ = std::move(other.alloc_); return true; } // If the allocator does not propagate then copy the data from other. template :: propagate_on_container_move_assignment::value)> FMT_CONSTEXPR20 auto move_alloc(basic_memory_buffer& other) -> bool { T* data = other.data(); if (alloc_ == other.alloc_ || data == other.store_) return true; size_t size = other.size(); // Perform copy operation, allocators are different. this->resize(size); detail::copy(data, data + size, this->data()); return false; } // Move data from other to this buffer. FMT_CONSTEXPR20 void move(basic_memory_buffer& other) { T* data = other.data(); size_t size = other.size(), capacity = other.capacity(); if (!move_alloc(other)) return; if (data == other.store_) { this->set(store_, capacity); detail::copy(other.store_, other.store_ + size, store_); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called // when deallocating. other.set(other.store_, 0); other.clear(); } this->resize(size); } public: /// Constructs a `basic_memory_buffer` object moving the content of the other /// object to it. FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept : detail::buffer(grow) { move(other); } /// Moves the content of the other `basic_memory_buffer` object to this one. auto operator=(basic_memory_buffer&& other) 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. FMT_CONSTEXPR 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); } using detail::buffer::append; template FMT_CONSTEXPR20 void append(const ContiguousRange& range) { append(range.data(), range.data() + range.size()); } }; using memory_buffer = basic_memory_buffer; template FMT_NODISCARD auto to_string(const basic_memory_buffer& buf) -> std::string { auto size = buf.size(); detail::assume(size < std::string().max_size()); return {buf.data(), size}; } // A writer to a buffered stream. It doesn't own the underlying stream. class writer { private: detail::buffer* buf_; // We cannot create a file buffer in advance because any write to a FILE may // invalidate it. FILE* file_; public: inline writer(FILE* f) : buf_(nullptr), file_(f) {} inline writer(detail::buffer& buf) : buf_(&buf) {} /// Formats `args` according to specifications in `fmt` and writes the /// output to the file. template void print(format_string fmt, T&&... args) { if (buf_) fmt::format_to(appender(*buf_), fmt, std::forward(args)...); else fmt::print(file_, fmt, std::forward(args)...); } }; class string_buffer { private: std::string str_; detail::container_buffer buf_; public: inline string_buffer() : buf_(str_) {} inline operator writer() { return buf_; } inline auto str() -> std::string& { return str_; } }; template struct is_contiguous> : std::true_type { }; // Suppress a misleading warning in older versions of clang. FMT_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") /// An error reported from a formatting function. class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { public: using std::runtime_error::runtime_error; }; class loc_value; FMT_END_EXPORT namespace detail { FMT_API auto write_console(int fd, string_view text) -> bool; FMT_API void print(FILE*, string_view); } // namespace detail namespace detail { template struct fixed_string { FMT_CONSTEXPR20 fixed_string(const Char (&s)[N]) { detail::copy(static_cast(s), s + N, data); } Char data[N] = {}; }; // Converts a compile-time string to basic_string_view. FMT_EXPORT 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)}; } FMT_EXPORT template constexpr auto compile_string_to_view(basic_string_view s) -> basic_string_view { return s; } // 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)> constexpr auto is_negative(T value) -> bool { return value < 0; } template ::value)> constexpr auto is_negative(T) -> bool { return false; } // 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 // Converts value in the range [0, 100) to a string. // GCC generates slightly better code when value is pointer-size. inline auto digits2(size_t value) -> const char* { // Align data since unaligned access may be slower when crossing a // hardware-specific boundary. alignas(2) static const char data[] = "0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" "4041424344454647484950515253545556575859" "6061626364656667686970717273747576777879" "8081828384858687888990919293949596979899"; return &data[value * 2]; } template constexpr auto getsign(sign s) -> Char { return static_cast(((' ' << 24) | ('+' << 16) | ('-' << 8)) >> (static_cast(s) * 8)); } 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_opt n) -> int { return count_digits_fallback(n); } #endif #ifdef FMT_BUILTIN_CLZLL // It is a separate function rather than a part of count_digits to workaround // the lack of static constexpr in constexpr functions. inline auto do_count_digits(uint64_t n) -> int { // This has comparable performance to the version by Kendall Willets // (https://github.com/fmtlib/format-benchmark/blob/master/digits10) // but uses smaller tables. // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). static constexpr uint8_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]; static constexpr 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 // 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() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); #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 (!is_constant_evaluated() && num_bits() == 32) return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; #endif // Lambda avoids unreachable code warnings from NVHPC. return [](UInt m) { int num_digits = 0; do { ++num_digits; } while ((m >>= BITS) != 0); return num_digits; }(n); } #ifdef FMT_BUILTIN_CLZ // It is a separate function rather than a part of count_digits to workaround // the lack of static constexpr in constexpr functions. FMT_INLINE auto do_count_digits(uint32_t n) -> int { // 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 }; auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31]; return static_cast((n + inc) >> 32); } #endif // 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() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); #endif return count_digits_fallback(n); } template constexpr auto digits10() noexcept -> int { return std::numeric_limits::digits10; } template <> constexpr auto digits10() noexcept -> int { return 38; } template <> constexpr auto digits10() 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); } #ifndef FMT_HEADER_ONLY FMT_BEGIN_EXPORT extern template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result; extern template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result; extern template FMT_API auto decimal_point_impl(locale_ref) -> char; extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; FMT_END_EXPORT #endif // FMT_HEADER_ONLY // Compares two characters for equality. template auto equal2(const Char* lhs, const char* rhs) -> bool { return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); } inline auto equal2(const char* lhs, const char* rhs) -> bool { return memcmp(lhs, rhs, 2) == 0; } // Writes a two-digit value to out. template FMT_CONSTEXPR20 FMT_INLINE void write2digits(Char* out, size_t value) { if (!is_constant_evaluated() && std::is_same::value && !FMT_OPTIMIZE_SIZE) { memcpy(out, digits2(value), 2); return; } *out++ = static_cast('0' + value / 10); *out = static_cast('0' + value % 10); } // Formats a decimal unsigned integer value writing to out pointing to a buffer // of specified size. The caller must ensure that the buffer is large enough. template FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size) -> Char* { FMT_ASSERT(size >= count_digits(value), "invalid digit count"); unsigned n = to_unsigned(size); 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. n -= 2; write2digits(out + n, static_cast(value % 100)); value /= 100; } if (value >= 10) { n -= 2; write2digits(out + n, static_cast(value)); } else { out[--n] = static_cast('0' + value); } return out + n; } template FMT_CONSTEXPR FMT_INLINE auto format_decimal(Char* out, UInt value, int num_digits) -> Char* { do_format_decimal(out, value, num_digits); return out + num_digits; } template >::value)> FMT_CONSTEXPR auto format_decimal(OutputIt out, UInt value, int num_digits) -> OutputIt { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { do_format_decimal(ptr, value, num_digits); return out; } // Buffer is large enough to hold all digits (digits10 + 1). char buffer[digits10() + 1]; if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); do_format_decimal(buffer, value, num_digits); return copy_noinline(buffer, buffer + num_digits, out); } template FMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value, int size, bool upper = false) -> Char* { out += size; do { const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; unsigned digit = static_cast(value & ((1u << base_bits) - 1)); *--out = static_cast(base_bits < 4 ? static_cast('0' + digit) : digits[digit]); } while ((value >>= base_bits) != 0); return out; } // Formats an unsigned integer in the power of two base (binary, octal, hex). template FMT_CONSTEXPR auto format_base2e(int base_bits, Char* out, UInt value, int num_digits, bool upper = false) -> Char* { do_format_base2e(base_bits, out, value, num_digits, upper); return out + num_digits; } template ::value)> FMT_CONSTEXPR inline auto format_base2e(int base_bits, OutputIt out, UInt value, int num_digits, bool upper = false) -> OutputIt { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { format_base2e(base_bits, ptr, value, num_digits, upper); return out; } // Make buffer large enough for any base. char buffer[num_bits()]; if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); format_base2e(base_bits, buffer, value, num_digits, upper); return detail::copy_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); inline operator basic_string_view() const { return {&buffer_[0], size()}; } inline auto size() const -> size_t { return buffer_.size() - 1; } inline auto c_str() const -> const wchar_t* { return &buffer_[0]; } inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; enum class to_utf8_error_policy { abort, replace }; // A converter from UTF-16/UTF-32 (host endian) to UTF-8. template class to_utf8 { private: Buffer buffer_; public: to_utf8() {} explicit to_utf8(basic_string_view s, to_utf8_error_policy policy = to_utf8_error_policy::abort) { static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, "expected utf16 or utf32"); if (!convert(s, policy)) { FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" : "invalid utf32")); } } operator string_view() const { return string_view(&buffer_[0], size()); } auto size() const -> size_t { return buffer_.size() - 1; } auto c_str() const -> const char* { return &buffer_[0]; } auto str() const -> std::string { return std::string(&buffer_[0], size()); } // Performs conversion returning a bool instead of throwing exception on // conversion error. This method may still throw in case of memory allocation // error. auto convert(basic_string_view s, to_utf8_error_policy policy = to_utf8_error_policy::abort) -> bool { if (!convert(buffer_, s, policy)) return false; buffer_.push_back(0); return true; } static auto convert(Buffer& buf, basic_string_view s, to_utf8_error_policy policy = to_utf8_error_policy::abort) -> bool { for (auto p = s.begin(); p != s.end(); ++p) { uint32_t c = static_cast(*p); if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { // Handle a surrogate pair. ++p; if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { if (policy == to_utf8_error_policy::abort) return false; buf.append(string_view("\xEF\xBF\xBD")); --p; continue; } c = (c << 10) + static_cast(*p) - 0x35fdc00; } if (c < 0x80) { buf.push_back(static_cast(c)); } else if (c < 0x800) { buf.push_back(static_cast(0xc0 | (c >> 6))); buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { buf.push_back(static_cast(0xe0 | (c >> 12))); buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if (c >= 0x10000 && c <= 0x10ffff) { buf.push_back(static_cast(0xf0 | (c >> 18))); buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); buf.push_back(static_cast(0x80 | (c & 0x3f))); } else { return false; } } return true; } }; // Computes 128-bit result of multiplication of two 64-bit unsigned integers. FMT_INLINE auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { #if FMT_USE_INT128 auto p = static_cast(x) * static_cast(y); return {static_cast(p >> 64), static_cast(p)}; #elif defined(_MSC_VER) && defined(_M_X64) auto hi = uint64_t(); auto lo = _umul128(x, y, &hi); return {hi, lo}; #else const uint64_t mask = static_cast(max_value()); 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 } namespace dragonbox { // Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from // https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. inline auto floor_log10_pow2(int e) noexcept -> int { FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); return (e * 315653) >> 20; } inline auto floor_log2_pow10(int e) noexcept -> int { FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); return (e * 1741647) >> 19; } // Computes upper 64 bits of multiplication of two 64-bit unsigned integers. inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { #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 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept -> uint128_fallback { uint128_fallback r = umul128(x, y.high()); r += umul128_upper64(x, y.low()); return r; } FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; // Type-specific information that Dragonbox uses. template struct float_info; template <> struct float_info { using carrier_uint = uint32_t; static const int exponent_bits = 8; 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 shorter_interval_tie_lower_threshold = -35; static const int shorter_interval_tie_upper_threshold = -35; }; template <> struct float_info { using carrier_uint = uint64_t; static const int exponent_bits = 11; 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 = 341; static const int shorter_interval_tie_lower_threshold = -77; static const int shorter_interval_tie_upper_threshold = -77; }; // An 80- or 128-bit floating point number. template struct float_info::digits == 64 || std::numeric_limits::digits == 113 || is_float128::value>> { using carrier_uint = detail::uint128_t; static const int exponent_bits = 15; }; // A double-double floating point number. template struct float_info::value>> { using carrier_uint = detail::uint128_t; }; 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) noexcept -> decimal_fp; } // namespace dragonbox // Returns true iff Float has the implicit bit which is not stored. template constexpr auto has_implicit_bit() -> bool { // An 80-bit FP number has a 64-bit significand an no implicit bit. return std::numeric_limits::digits != 64; } // Returns the number of significand bits stored in Float. The implicit bit is // not counted since it is not stored. template constexpr auto num_significand_bits() -> int { // std::numeric_limits may not support __float128. return is_float128() ? 112 : (std::numeric_limits::digits - (has_implicit_bit() ? 1 : 0)); } template constexpr auto exponent_mask() -> typename dragonbox::float_info::carrier_uint { using float_uint = typename dragonbox::float_info::carrier_uint; return ((float_uint(1) << dragonbox::float_info::exponent_bits) - 1) << num_significand_bits(); } template constexpr auto exponent_bias() -> int { // std::numeric_limits may not support __float128. return is_float128() ? 16383 : std::numeric_limits::max_exponent - 1; } FMT_CONSTEXPR inline auto compute_exp_size(int exp) -> int { auto prefix_size = 2; // sign + 'e' auto abs_exp = exp >= 0 ? exp : -exp; if (abs_exp < 100) return prefix_size + 2; return prefix_size + (abs_exp >= 1000 ? 4 : 3); } // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. template FMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt { FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); if (exp < 0) { *out++ = static_cast('-'); exp = -exp; } else { *out++ = static_cast('+'); } auto uexp = static_cast(exp); if (is_constant_evaluated()) { if (uexp < 10) *out++ = '0'; return format_decimal(out, uexp, count_digits(uexp)); } if (uexp >= 100u) { const char* top = digits2(uexp / 100); if (uexp >= 1000u) *out++ = static_cast(top[0]); *out++ = static_cast(top[1]); uexp %= 100; } const char* d = digits2(uexp); *out++ = static_cast(d[0]); *out++ = static_cast(d[1]); return out; } // A floating-point number f * pow(2, e) where F is an unsigned type. template struct basic_fp { F f; int e; static constexpr int num_significand_bits = static_cast(sizeof(F) * num_bits()); constexpr basic_fp() : f(0), e(0) {} constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} // Constructs fp from an IEEE754 floating-point number. template FMT_CONSTEXPR basic_fp(Float n) { assign(n); } // Assigns n to this and return true iff predecessor is closer than successor. template ::value)> FMT_CONSTEXPR auto assign(Float n) -> bool { static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); // Assume Float is in the format [sign][exponent][significand]. using carrier_uint = typename dragonbox::float_info::carrier_uint; const auto num_float_significand_bits = detail::num_significand_bits(); const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; const auto significand_mask = implicit_bit - 1; auto u = bit_cast(n); f = static_cast(u & significand_mask); auto biased_e = static_cast((u & exponent_mask()) >> num_float_significand_bits); // The predecessor is closer if n is a normalized power of 2 (f == 0) // other than the smallest normalized number (biased_e > 1). auto is_predecessor_closer = f == 0 && biased_e > 1; if (biased_e == 0) biased_e = 1; // Subnormals use biased exponent 1 (min exponent). else if (has_implicit_bit()) f += static_cast(implicit_bit); e = biased_e - exponent_bias() - num_float_significand_bits; if (!has_implicit_bit()) ++e; return is_predecessor_closer; } template ::value)> FMT_CONSTEXPR auto assign(Float n) -> bool { static_assert(std::numeric_limits::is_iec559, "unsupported FP"); return assign(static_cast(n)); } }; using fp = basic_fp; // Normalizes the value converted from double and multiplied by (1 << SHIFT). template FMT_CONSTEXPR auto normalize(basic_fp value) -> basic_fp { // Handle subnormals. const auto implicit_bit = F(1) << num_significand_bits(); const auto shifted_implicit_bit = 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 = basic_fp::num_significand_bits - num_significand_bits() - SHIFT - 1; value.f <<= offset; value.e -= offset; return value; } // Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { #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 } FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { return {multiply(x.f, y.f), x.e + y.e + 64}; } template () == num_bits()> using convert_float_result = conditional_t::value || doublish, double, T>; template constexpr auto convert_float(T value) -> convert_float_result { return static_cast>(value); } template auto select(T true_value, F) -> T { return true_value; } template auto select(T, F false_value) -> F { return false_value; } template FMT_CONSTEXPR FMT_NOINLINE auto fill(OutputIt it, size_t n, const basic_specs& specs) -> OutputIt { auto fill_size = specs.fill_size(); if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit()); if (const Char* data = specs.fill()) { for (size_t i = 0; i < n; ++i) it = copy(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 format_specs& specs, size_t size, size_t width, F&& f) -> OutputIt { static_assert(default_align == align::left || default_align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); size_t padding = spec_width > width ? spec_width - width : 0; // Shifts are encoded as string literals because static constexpr is not // supported in constexpr functions. auto* shifts = default_align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; size_t left_padding = padding >> shifts[static_cast(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); it = f(it); if (right_padding != 0) it = fill(it, right_padding, specs); return base_iterator(out, it); } template constexpr auto write_padded(OutputIt out, const 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 format_specs& specs = {}) -> OutputIt { return write_padded( out, specs, bytes.size(), [bytes](reserve_iterator it) { const char* data = bytes.data(); return copy(data, data + bytes.size(), it); }); } template auto write_ptr(OutputIt out, UIntPtr value, const 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_base2e(4, it, value, num_digits); }; return specs ? write_padded(out, *specs, size, write) : base_iterator(out, write(reserve(out, size))); } // Returns true iff the code point cp is printable. FMT_API auto is_printable(uint32_t cp) -> bool; inline auto needs_escape(uint32_t cp) -> bool { if (cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\') return true; if (const_check(FMT_OPTIMIZE_SIZE > 1)) return false; return !is_printable(cp); } template struct find_escape_result { const Char* begin; const Char* end; uint32_t cp; }; template auto find_escape(const Char* begin, const Char* end) -> find_escape_result { for (; begin != end; ++begin) { uint32_t cp = static_cast>(*begin); if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; if (needs_escape(cp)) return {begin, begin + 1, cp}; } return {begin, nullptr, 0}; } inline auto find_escape(const char* begin, const char* end) -> find_escape_result { if (const_check(!use_utf8)) return find_escape(begin, end); auto result = find_escape_result{end, nullptr, 0}; for_each_codepoint(string_view(begin, to_unsigned(end - begin)), [&](uint32_t cp, string_view sv) { if (needs_escape(cp)) { result = {sv.begin(), sv.end(), cp}; return false; } return true; }); return result; } template auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { *out++ = static_cast('\\'); *out++ = static_cast(prefix); Char buf[width]; fill_n(buf, width, static_cast('0')); format_base2e(4, buf, cp, width); return copy(buf, buf + width, out); } template auto write_escaped_cp(OutputIt out, const find_escape_result& escape) -> OutputIt { auto c = static_cast(escape.cp); switch (escape.cp) { case '\n': *out++ = static_cast('\\'); c = static_cast('n'); break; case '\r': *out++ = static_cast('\\'); c = static_cast('r'); break; case '\t': *out++ = static_cast('\\'); c = static_cast('t'); break; case '"': FMT_FALLTHROUGH; case '\'': FMT_FALLTHROUGH; case '\\': *out++ = static_cast('\\'); break; default: if (escape.cp < 0x100) return write_codepoint<2, Char>(out, 'x', escape.cp); if (escape.cp < 0x10000) return write_codepoint<4, Char>(out, 'u', escape.cp); if (escape.cp < 0x110000) return write_codepoint<8, Char>(out, 'U', escape.cp); for (Char escape_char : basic_string_view( escape.begin, to_unsigned(escape.end - escape.begin))) { out = write_codepoint<2, Char>(out, 'x', static_cast(escape_char) & 0xFF); } return out; } *out++ = c; return out; } template auto write_escaped_string(OutputIt out, basic_string_view str) -> OutputIt { *out++ = static_cast('"'); auto begin = str.begin(), end = str.end(); do { auto escape = find_escape(begin, end); out = copy(begin, escape.begin, out); begin = escape.end; if (!begin) break; out = write_escaped_cp(out, escape); } while (begin != end); *out++ = static_cast('"'); return out; } template auto write_escaped_char(OutputIt out, Char v) -> OutputIt { Char v_array[1] = {v}; *out++ = static_cast('\''); if ((needs_escape(static_cast(v)) && v != static_cast('"')) || v == static_cast('\'')) { out = write_escaped_cp(out, find_escape_result{v_array, v_array + 1, static_cast(v)}); } else { *out++ = v; } *out++ = static_cast('\''); return out; } template FMT_CONSTEXPR auto write_char(OutputIt out, Char value, const format_specs& specs) -> OutputIt { bool is_debug = specs.type() == presentation_type::debug; return write_padded(out, specs, 1, [=](reserve_iterator it) { if (is_debug) return write_escaped_char(it, value); *it++ = value; return it; }); } template class digit_grouping { private: std::string grouping_; std::basic_string thousands_sep_; struct next_state { std::string::const_iterator group; int pos; }; auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } // Returns the next digit group separator position. auto next(next_state& state) const -> int { if (thousands_sep_.empty()) return max_value(); if (state.group == grouping_.end()) return state.pos += grouping_.back(); if (*state.group <= 0 || *state.group == max_value()) return max_value(); state.pos += *state.group++; return state.pos; } public: explicit digit_grouping(locale_ref loc, bool localized = true) { if (!localized) return; auto sep = thousands_sep(loc); grouping_ = sep.grouping; if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } digit_grouping(std::string grouping, std::basic_string sep) : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} auto has_separator() const -> bool { return !thousands_sep_.empty(); } auto count_separators(int num_digits) const -> int { int count = 0; auto state = initial_state(); while (num_digits > next(state)) ++count; return count; } // Applies grouping to digits and writes the output to out. template auto apply(Out out, basic_string_view digits) const -> Out { auto num_digits = static_cast(digits.size()); auto separators = basic_memory_buffer(); separators.push_back(0); auto state = initial_state(); while (int i = next(state)) { if (i >= num_digits) break; separators.push_back(i); } for (int i = 0, sep_index = static_cast(separators.size() - 1); i < num_digits; ++i) { if (num_digits - i == separators[sep_index]) { out = copy(thousands_sep_.data(), thousands_sep_.data() + thousands_sep_.size(), out); --sep_index; } *out++ = static_cast(digits[to_unsigned(i)]); } return out; } }; FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { prefix |= prefix != 0 ? value << 8 : value; prefix += (1u + (value > 0xff ? 1 : 0)) << 24; } // Writes a decimal integer with digit grouping. template auto write_int(OutputIt out, UInt value, unsigned prefix, const format_specs& specs, const digit_grouping& grouping) -> OutputIt { static_assert(std::is_same, UInt>::value, ""); int num_digits = 0; auto buffer = memory_buffer(); switch (specs.type()) { default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; case presentation_type::none: case presentation_type::dec: num_digits = count_digits(value); format_decimal(appender(buffer), value, num_digits); break; case presentation_type::hex: if (specs.alt()) prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); num_digits = count_digits<4>(value); format_base2e(4, appender(buffer), value, num_digits, specs.upper()); break; case presentation_type::oct: num_digits = count_digits<3>(value); // Octal prefix '0' is counted as a digit, so only add it if precision // is not greater than the number of digits. if (specs.alt() && specs.precision <= num_digits && value != 0) prefix_append(prefix, '0'); format_base2e(3, appender(buffer), value, num_digits); break; case presentation_type::bin: if (specs.alt()) prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); num_digits = count_digits<1>(value); format_base2e(1, appender(buffer), value, num_digits); break; case presentation_type::chr: return write_char(out, static_cast(value), specs); } unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + to_unsigned(grouping.count_separators(num_digits)); return write_padded( out, specs, size, size, [&](reserve_iterator it) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); return grouping.apply(it, string_view(buffer.data(), buffer.size())); }); } #if FMT_USE_LOCALE // Writes a localized value. FMT_API auto write_loc(appender out, loc_value value, const format_specs& specs, locale_ref loc) -> bool; auto write_loc(basic_appender out, loc_value value, const format_specs& specs, locale_ref loc) -> bool; #endif template inline auto write_loc(OutputIt, const loc_value&, const format_specs&, locale_ref) -> bool { return false; } template struct write_int_arg { UInt abs_value; unsigned prefix; }; template FMT_CONSTEXPR auto make_write_int_arg(T value, sign s) -> write_int_arg> { auto prefix = 0u; auto abs_value = static_cast>(value); if (is_negative(value)) { prefix = 0x01000000 | '-'; abs_value = 0 - abs_value; } else { constexpr unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '}; prefix = prefixes[static_cast(s)]; } return {abs_value, prefix}; } template struct loc_writer { basic_appender out; const format_specs& specs; std::basic_string sep; std::string grouping; std::basic_string decimal_point; template ::value)> auto operator()(T value) -> bool { auto arg = make_write_int_arg(value, specs.sign()); write_int(out, static_cast>(arg.abs_value), arg.prefix, specs, digit_grouping(grouping, sep)); return true; } template ::value)> auto operator()(T) -> bool { return false; } }; // Size and padding computation separate from write_int to avoid template bloat. struct size_padding { unsigned size; unsigned padding; FMT_CONSTEXPR size_padding(int num_digits, unsigned prefix, const 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); } } }; template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, const format_specs& specs) -> OutputIt { static_assert(std::is_same>::value, ""); constexpr size_t buffer_size = num_bits(); char buffer[buffer_size]; if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\0'); const char* begin = nullptr; const char* end = buffer + buffer_size; auto abs_value = arg.abs_value; auto prefix = arg.prefix; switch (specs.type()) { default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; case presentation_type::none: case presentation_type::dec: begin = do_format_decimal(buffer, abs_value, buffer_size); break; case presentation_type::hex: begin = do_format_base2e(4, buffer, abs_value, buffer_size, specs.upper()); if (specs.alt()) prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); break; case presentation_type::oct: { begin = do_format_base2e(3, buffer, abs_value, buffer_size); // Octal prefix '0' is counted as a digit, so only add it if precision // is not greater than the number of digits. auto num_digits = end - begin; if (specs.alt() && specs.precision <= num_digits && abs_value != 0) prefix_append(prefix, '0'); break; } case presentation_type::bin: begin = do_format_base2e(1, buffer, abs_value, buffer_size); if (specs.alt()) prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); break; case presentation_type::chr: return write_char(out, static_cast(abs_value), specs); } // Write an integer in the format // // prefix contains chars in three lower bytes and the size in the fourth byte. int num_digits = static_cast(end - begin); // 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)); for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); return base_iterator(out, copy(begin, end, it)); } auto sp = size_padding(num_digits, prefix, specs); unsigned padding = sp.padding; return write_padded( out, specs, sp.size, [=](reserve_iterator it) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); it = detail::fill_n(it, padding, static_cast('0')); return copy(begin, end, it); }); } template FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out, write_int_arg arg, const format_specs& specs) -> OutputIt { return write_int(out, arg, specs); } template ::value && !std::is_same::value && !std::is_same::value)> FMT_CONSTEXPR FMT_INLINE auto write(basic_appender out, T value, const format_specs& specs, locale_ref loc) -> basic_appender { if (specs.localized() && write_loc(out, value, specs, loc)) return out; return write_int_noinline(out, make_write_int_arg(value, specs.sign()), specs); } // An inlined version of write used in format string compilation. template ::value && !std::is_same::value && !std::is_same::value && !std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, const format_specs& specs, locale_ref loc) -> OutputIt { if (specs.localized() && write_loc(out, value, specs, loc)) return out; return write_int(out, make_write_int_arg(value, specs.sign()), specs); } template FMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs, locale_ref loc = {}) -> OutputIt { // char is formatted as unsigned char for consistency across platforms. using unsigned_type = conditional_t::value, unsigned char, unsigned>; return check_char_specs(specs) ? write_char(out, value, specs) : write(out, static_cast(value), specs, loc); } template ::value)> FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, const format_specs& specs) -> OutputIt { bool is_debug = specs.type() == presentation_type::debug; if (specs.precision < 0 && specs.width == 0) { auto&& it = reserve(out, s.size()); return is_debug ? write_escaped_string(it, s) : copy(s, it); } size_t display_width_limit = specs.precision < 0 ? SIZE_MAX : to_unsigned(specs.precision); size_t display_width = !is_debug || specs.precision == 0 ? 0 : 1; // Account for opening '"'. size_t size = !is_debug || specs.precision == 0 ? 0 : 1; for_each_codepoint(s, [&](uint32_t cp, string_view sv) { if (is_debug && needs_escape(cp)) { counting_buffer buf; write_escaped_cp(basic_appender(buf), find_escape_result{sv.begin(), sv.end(), cp}); // We're reinterpreting bytes as display width. That's okay // because write_escaped_cp() only writes ASCII characters. size_t cp_width = buf.count(); if (display_width + cp_width <= display_width_limit) { display_width += cp_width; size += cp_width; // If this is the end of the string, account for closing '"'. if (display_width < display_width_limit && sv.end() == s.end()) { ++display_width; ++size; } return true; } size += display_width_limit - display_width; display_width = display_width_limit; return false; } size_t cp_width = display_width_of(cp); if (cp_width + display_width <= display_width_limit) { display_width += cp_width; size += sv.size(); // If this is the end of the string, account for closing '"'. if (is_debug && display_width < display_width_limit && sv.end() == s.end()) { ++display_width; ++size; } return true; } return false; }); struct bounded_output_iterator { reserve_iterator underlying_iterator; size_t bound; FMT_CONSTEXPR auto operator*() -> bounded_output_iterator& { return *this; } FMT_CONSTEXPR auto operator++() -> bounded_output_iterator& { return *this; } FMT_CONSTEXPR auto operator++(int) -> bounded_output_iterator& { return *this; } FMT_CONSTEXPR auto operator=(char c) -> bounded_output_iterator& { if (bound > 0) { *underlying_iterator++ = c; --bound; } return *this; } }; return write_padded( out, specs, size, display_width, [=](reserve_iterator it) { return is_debug ? write_escaped_string(bounded_output_iterator{it, size}, s) .underlying_iterator : copy(s.data(), s.data() + size, it); }); } template ::value)> FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, const format_specs& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) size = to_unsigned(specs.precision); bool is_debug = specs.type() == presentation_type::debug; if (is_debug) { auto buf = counting_buffer(); write_escaped_string(basic_appender(buf), s); size = buf.count(); } return write_padded( out, specs, size, [=](reserve_iterator it) { return is_debug ? write_escaped_string(it, s) : copy(data, data + size, it); }); } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, const format_specs& specs, locale_ref) -> OutputIt { return write(out, s, specs); } template FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, locale_ref) -> OutputIt { if (specs.type() == presentation_type::pointer) return write_ptr(out, bit_cast(s), &specs); if (!s) report_error("string pointer is null"); return write(out, basic_string_view(s), specs, {}); } 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); if (auto ptr = to_pointer(out, size)) { if (negative) *ptr++ = static_cast('-'); format_decimal(ptr, abs_value, num_digits); return out; } if (negative) *out++ = static_cast('-'); return format_decimal(out, abs_value, num_digits); } template FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, format_specs& specs) -> const Char* { FMT_ASSERT(begin != end, ""); auto alignment = align::none; auto p = begin + code_point_length(begin); if (end - p <= 0) p = begin; for (;;) { switch (to_ascii(*p)) { case '<': alignment = align::left; break; case '>': alignment = align::right; break; case '^': alignment = align::center; break; } if (alignment != align::none) { if (p != begin) { auto c = *begin; if (c == '}') return begin; if (c == '{') { report_error("invalid fill character '{'"); return begin; } specs.set_fill(basic_string_view(begin, to_unsigned(p - begin))); begin = p + 1; } else { ++begin; } break; } else if (p == begin) { break; } p = begin; } specs.set_align(alignment); return begin; } template FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, format_specs specs, sign s) -> OutputIt { auto str = isnan ? (specs.upper() ? "NAN" : "nan") : (specs.upper() ? "INF" : "inf"); constexpr size_t str_size = 3; auto size = str_size + (s != sign::none ? 1 : 0); // Replace '0'-padding with space for non-finite values. const bool is_zero_fill = specs.fill_size() == 1 && specs.fill_unit() == '0'; if (is_zero_fill) specs.set_fill(' '); return write_padded(out, specs, size, [=](reserve_iterator it) { if (s != sign::none) *it++ = detail::getsign(s); return copy(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; }; constexpr auto get_significand_size(const big_decimal_fp& f) -> int { return f.significand_size; } template inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { return count_digits(f.significand); } template constexpr auto write_significand(OutputIt out, const char* significand, int significand_size) -> OutputIt { return copy(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); } template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int exponent, const Grouping& grouping) -> OutputIt { if (!grouping.has_separator()) { out = write_significand(out, significand, significand_size); return detail::fill_n(out, exponent, static_cast('0')); } auto buffer = memory_buffer(); write_significand(appender(buffer), significand, significand_size); detail::fill_n(appender(buffer), exponent, '0'); return grouping.apply(out, string_view(buffer.data(), buffer.size())); } 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); out += significand_size + 1; Char* end = out; int floating_size = significand_size - integral_size; for (int i = floating_size / 2; i > 0; --i) { out -= 2; write2digits(out, static_cast(significand % 100)); significand /= 100; } if (floating_size % 2 != 0) { *--out = static_cast('0' + significand % 10); significand /= 10; } *--out = decimal_point; format_decimal(out - integral_size, significand, integral_size); 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_noinline(buffer, end, out); } template FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, int significand_size, int integral_size, Char decimal_point) -> OutputIt { out = detail::copy_noinline(significand, significand + integral_size, out); if (!decimal_point) return out; *out++ = decimal_point; return detail::copy_noinline(significand + integral_size, significand + significand_size, out); } template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int integral_size, Char decimal_point, const Grouping& grouping) -> OutputIt { if (!grouping.has_separator()) { return write_significand(out, significand, significand_size, integral_size, decimal_point); } auto buffer = basic_memory_buffer(); write_significand(basic_appender(buffer), significand, significand_size, integral_size, decimal_point); grouping.apply( out, basic_string_view(buffer.data(), to_unsigned(integral_size))); return detail::copy_noinline(buffer.data() + integral_size, buffer.end(), out); } // Numbers with exponents greater or equal to the returned value will use // the exponential notation. template FMT_CONSTEVAL auto exp_upper() -> int { return std::numeric_limits::digits10 != 0 ? min_of(16, std::numeric_limits::digits10 + 1) : 16; } // Use the fixed notation if the exponent is in [-4, exp_upper), // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. constexpr auto use_fixed(int exp, int exp_upper) -> bool { return exp >= -4 && exp < exp_upper; } template class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} constexpr auto has_separator() const -> bool { return false; } constexpr auto count_separators(int) const -> int { return 0; } template constexpr auto apply(Out out, basic_string_view) const -> Out { return out; } }; template FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f, int significand_size, Char decimal_point, const format_specs& specs, sign s, locale_ref loc = {}) -> OutputIt { using iterator = reserve_iterator; int exp = f.exponent + significand_size; long long size = significand_size + (s != sign::none ? 1 : 0); if (f.exponent >= 0) { // 1234e5 -> 123400000[.0+] size += f.exponent; int num_zeros = specs.precision - exp; abort_fuzzing_if(num_zeros > 5000); if (specs.alt()) { ++size; if (num_zeros <= 0 && specs.type() != presentation_type::fixed) num_zeros = 0; if (num_zeros > 0) size += num_zeros; } auto grouping = Grouping(loc, specs.localized()); size += grouping.count_separators(exp); return write_padded( out, specs, static_cast(size), [&](iterator it) { if (s != sign::none) *it++ = detail::getsign(s); it = write_significand(it, f.significand, significand_size, f.exponent, grouping); if (!specs.alt()) return it; *it++ = decimal_point; return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it; }); } if (exp > 0) { // 1234e-2 -> 12.34[0+] int num_zeros = specs.alt() ? specs.precision - significand_size : 0; size += 1 + max_of(num_zeros, 0); auto grouping = Grouping(loc, specs.localized()); size += grouping.count_separators(exp); return write_padded( out, specs, to_unsigned(size), [&](iterator it) { if (s != sign::none) *it++ = detail::getsign(s); it = write_significand(it, f.significand, significand_size, exp, decimal_point, grouping); return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it; }); } // 1234e-6 -> 0.001234 int num_zeros = -exp; if (significand_size == 0 && specs.precision >= 0 && specs.precision < num_zeros) { num_zeros = specs.precision; } bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); size += 1 + (pointy ? 1 : 0) + num_zeros; return write_padded( out, specs, to_unsigned(size), [&](iterator it) { if (s != sign::none) *it++ = detail::getsign(s); *it++ = Char('0'); if (!pointy) return it; *it++ = decimal_point; it = detail::fill_n(it, num_zeros, Char('0')); return write_significand(it, f.significand, significand_size); }); } template FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, const format_specs& specs, sign s, int exp_upper, locale_ref loc) -> OutputIt { Char point = specs.localized() ? detail::decimal_point(loc) : Char('.'); int significand_size = get_significand_size(f); int exp = f.exponent + significand_size - 1; if (specs.type() == presentation_type::fixed || (specs.type() != presentation_type::exp && use_fixed(exp, specs.precision > 0 ? specs.precision : exp_upper))) { return write_fixed(out, f, significand_size, point, specs, s, loc); } // Write value in the exponential format. int num_zeros = 0; long long size = significand_size + (s != sign::none ? 1 : 0); if (specs.alt()) { num_zeros = max_of(specs.precision - significand_size, 0); size += num_zeros; } else if (significand_size == 1) { point = Char(); } size += (point ? 1 : 0) + compute_exp_size(exp); char exp_char = specs.upper() ? 'E' : 'e'; auto write = [=](reserve_iterator it) { if (s != sign::none) *it++ = detail::getsign(s); // Insert a decimal point after the first digit and add an exponent. it = write_significand(it, f.significand, significand_size, 1, point); if (num_zeros > 0) it = detail::fill_n(it, num_zeros, Char('0')); *it++ = Char(exp_char); return write_exponent(exp, it); }; auto usize = to_unsigned(size); return specs.width > 0 ? write_padded(out, specs, usize, write) : base_iterator(out, write(reserve(out, usize))); } template FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, const format_specs& specs, sign s, int exp_upper, locale_ref loc) -> OutputIt { if (is_constant_evaluated()) { return do_write_float>(out, f, specs, s, exp_upper, loc); } else { return do_write_float>(out, f, specs, s, exp_upper, loc); } } template constexpr auto isnan(T value) -> bool { return value != value; // std::isnan doesn't support __float128. } template struct has_isfinite : std::false_type {}; template struct has_isfinite> : std::true_type {}; template ::value&& has_isfinite::value)> FMT_CONSTEXPR20 auto isfinite(T value) -> bool { constexpr T inf = T(std::numeric_limits::infinity()); if (is_constant_evaluated()) return !detail::isnan(value) && value < inf && value > -inf; return std::isfinite(value); } template ::value)> FMT_CONSTEXPR auto isfinite(T value) -> bool { T inf = T(std::numeric_limits::infinity()); // std::isfinite doesn't support __float128. return !detail::isnan(value) && value < inf && value > -inf; } template ::value)> FMT_INLINE FMT_CONSTEXPR auto signbit(T value) -> bool { if (is_constant_evaluated()) { #ifdef __cpp_if_constexpr if constexpr (std::numeric_limits::is_iec559) { auto bits = detail::bit_cast(static_cast(value)); return (bits >> (num_bits() - 1)) != 0; } #endif } return std::signbit(static_cast(value)); } inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { // Adjust fixed precision by exponent because it is relative to decimal // point. if (exp10 > 0 && precision > max_value() - exp10) FMT_THROW(format_error("number is too big")); precision += exp10; } class bigint { private: // A bigint is a number in the form bigit_[N - 1] ... bigit_[0] * 32^exp_. using bigit = uint32_t; // A big digit. using double_bigit = uint64_t; enum { bigit_bits = num_bits() }; enum { bigits_capacity = 32 }; basic_memory_buffer bigits_; int exp_; friend struct formatter; FMT_CONSTEXPR auto get_bigit(int i) const -> bigit { return i >= exp_ && i < num_bigits() ? bigits_[i - exp_] : 0; } FMT_CONSTEXPR void subtract_bigits(int index, bigit other, bigit& borrow) { auto result = double_bigit(bigits_[index]) - other - borrow; bigits_[index] = static_cast(result); borrow = static_cast(result >> (bigit_bits * 2 - 1)); } FMT_CONSTEXPR void remove_leading_zeros() { int num_bigits = static_cast(bigits_.size()) - 1; while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; bigits_.resize(to_unsigned(num_bigits + 1)); } // Computes *this -= other assuming aligned bigints and *this >= other. FMT_CONSTEXPR 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); if (borrow != 0) subtract_bigits(i, 0, borrow); FMT_ASSERT(borrow == 0, ""); remove_leading_zeros(); } FMT_CONSTEXPR void multiply(uint32_t value) { bigit carry = 0; const double_bigit wide_value = value; 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); } template ::value || std::is_same::value)> FMT_CONSTEXPR void multiply(UInt value) { using half_uint = conditional_t::value, uint64_t, uint32_t>; const int shift = num_bits() - bigit_bits; const UInt lower = static_cast(value); const UInt upper = value >> num_bits(); UInt carry = 0; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { UInt result = lower * bigits_[i] + static_cast(carry); carry = (upper * bigits_[i] << shift) + (result >> bigit_bits) + (carry >> bigit_bits); bigits_[i] = static_cast(result); } while (carry != 0) { bigits_.push_back(static_cast(carry)); carry >>= bigit_bits; } } template ::value || std::is_same::value)> FMT_CONSTEXPR void assign(UInt n) { size_t num_bigits = 0; do { bigits_[num_bigits++] = static_cast(n); n >>= bigit_bits; } while (n != 0); bigits_.resize(num_bigits); exp_ = 0; } public: FMT_CONSTEXPR bigint() : exp_(0) {} explicit bigint(uint64_t n) { assign(n); } bigint(const bigint&) = delete; void operator=(const bigint&) = delete; FMT_CONSTEXPR void assign(const bigint& other) { auto size = other.bigits_.size(); bigits_.resize(size); auto data = other.bigits_.data(); copy(data, data + size, bigits_.data()); exp_ = other.exp_; } template FMT_CONSTEXPR void operator=(Int n) { FMT_ASSERT(n > 0, ""); assign(uint64_or_128_t(n)); } FMT_CONSTEXPR auto num_bigits() const -> int { return static_cast(bigits_.size()) + exp_; } FMT_CONSTEXPR auto operator<<=(int shift) -> bigint& { 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 FMT_CONSTEXPR auto operator*=(Int value) -> bigint& { FMT_ASSERT(value > 0, ""); multiply(uint32_or_64_or_128_t(value)); return *this; } friend FMT_CONSTEXPR auto compare(const bigint& b1, const bigint& b2) -> int { int num_bigits1 = b1.num_bigits(), num_bigits2 = b2.num_bigits(); if (num_bigits1 != num_bigits2) return num_bigits1 > num_bigits2 ? 1 : -1; int i = static_cast(b1.bigits_.size()) - 1; int j = static_cast(b2.bigits_.size()) - 1; int end = i - j; if (end < 0) end = 0; for (; i >= end; --i, --j) { bigit b1_bigit = b1.bigits_[i], b2_bigit = b2.bigits_[j]; if (b1_bigit != b2_bigit) return b1_bigit > b2_bigit ? 1 : -1; } if (i != j) return i > j ? 1 : -1; return 0; } // Returns compare(lhs1 + lhs2, rhs). friend FMT_CONSTEXPR auto add_compare(const bigint& lhs1, const bigint& lhs2, const bigint& rhs) -> int { int max_lhs_bigits = max_of(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; double_bigit borrow = 0; int min_exp = min_of(min_of(lhs1.exp_, lhs2.exp_), rhs.exp_); for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { double_bigit sum = double_bigit(lhs1.get_bigit(i)) + lhs2.get_bigit(i); bigit rhs_bigit = rhs.get_bigit(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. FMT_CONSTEXPR20 void assign_pow10(int exp) { FMT_ASSERT(exp >= 0, ""); if (exp == 0) return *this = 1; int bitmask = 1 << (num_bits() - countl_zero(static_cast(exp)) - 1); // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by // repeated squaring and multiplication. *this = 5; bitmask >>= 1; while (bitmask != 0) { square(); if ((exp & bitmask) != 0) *this *= 5; bitmask >>= 1; } *this <<= exp; // Multiply by pow(2, exp) by shifting. } FMT_CONSTEXPR20 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)); auto sum = uint128_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 += double_bigit(n[i]) * n[j]; } bigits_[bigit_index] = static_cast(sum); sum >>= num_bits(); // 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 += double_bigit(n[i++]) * n[j--]; bigits_[bigit_index] = static_cast(sum); sum >>= num_bits(); } 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. FMT_CONSTEXPR 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]; fill_n(bigits_.data(), to_unsigned(exp_difference), 0U); exp_ -= exp_difference; } // Divides this bignum by divisor, assigning the remainder to this and // returning the quotient. FMT_CONSTEXPR auto divmod_assign(const bigint& divisor) -> int { 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; } }; // format_dragon flags. enum dragon { predecessor_closer = 1, fixup = 2, // Run fixup to correct exp10 which can be off by one. fixed = 4, }; // Formats a floating-point number using a variation of the Fixed-Precision // Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // https://fmt.dev/papers/p372-steele.pdf. FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, unsigned flags, int num_digits, 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). // 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. bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0; int shift = is_predecessor_closer ? 2 : 1; if (value.e >= 0) { numerator = value.f; numerator <<= value.e + shift; lower = 1; lower <<= value.e; if (is_predecessor_closer) { upper_store = 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 (is_predecessor_closer) { upper_store.assign(numerator); upper_store <<= 1; upper = &upper_store; } numerator *= value.f; numerator <<= shift; denominator = 1; denominator <<= shift - value.e; } else { numerator = value.f; numerator <<= shift; denominator.assign_pow10(exp10); denominator <<= shift - value.e; lower = 1; if (is_predecessor_closer) { upper_store = 1ULL << 1; upper = &upper_store; } } int even = static_cast((value.f & 1) == 0); if (!upper) upper = &lower; bool shortest = num_digits < 0; if ((flags & dragon::fixup) != 0) { if (add_compare(numerator, *upper, denominator) + even <= 0) { --exp10; numerator *= 10; if (num_digits < 0) { lower *= 10; if (upper != &lower) *upper *= 10; } } if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); } // Invariant: value == (numerator / denominator) * pow(10, exp10). if (shortest) { // Generate the shortest representation. 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) { auto digit = '0'; if (num_digits == 0) { denominator *= 10; digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; } buf.push_back(digit); 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'; if ((flags & dragon::fixed) != 0) buf.push_back('0'); else ++exp10; } return; } ++digit; } buf[num_digits - 1] = static_cast('0' + digit); } // Formats a floating-point number using the hexfloat format. template ::value)> FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, buffer& buf) { // float is passed as double to reduce the number of instantiations and to // simplify implementation. static_assert(!std::is_same::value, ""); using info = dragonbox::float_info; // Assume Float is in the format [sign][exponent][significand]. using carrier_uint = typename info::carrier_uint; const auto num_float_significand_bits = detail::num_significand_bits(); basic_fp f(value); f.e += num_float_significand_bits; if (!has_implicit_bit()) --f.e; const auto num_fraction_bits = num_float_significand_bits + (has_implicit_bit() ? 1 : 0); const auto num_xdigits = (num_fraction_bits + 3) / 4; const auto leading_shift = ((num_xdigits - 1) * 4); const auto leading_mask = carrier_uint(0xF) << leading_shift; const auto leading_xdigit = static_cast((f.f & leading_mask) >> leading_shift); if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); int print_xdigits = num_xdigits - 1; if (specs.precision >= 0 && print_xdigits > specs.precision) { const int shift = ((print_xdigits - specs.precision - 1) * 4); const auto mask = carrier_uint(0xF) << shift; const auto v = static_cast((f.f & mask) >> shift); if (v >= 8) { const auto inc = carrier_uint(1) << (shift + 4); f.f += inc; f.f &= ~(inc - 1); } // Check long double overflow if (!has_implicit_bit()) { const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; if ((f.f & implicit_bit) == implicit_bit) { f.f >>= 4; f.e += 4; } } print_xdigits = specs.precision; } char xdigits[num_bits() / 4]; detail::fill_n(xdigits, sizeof(xdigits), '0'); format_base2e(4, xdigits, f.f, num_xdigits, specs.upper()); // Remove zero tail while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; buf.push_back('0'); buf.push_back(specs.upper() ? 'X' : 'x'); buf.push_back(xdigits[0]); if (specs.alt() || print_xdigits > 0 || print_xdigits < specs.precision) buf.push_back('.'); buf.append(xdigits + 1, xdigits + 1 + print_xdigits); for (; print_xdigits < specs.precision; ++print_xdigits) buf.push_back('0'); buf.push_back(specs.upper() ? 'P' : 'p'); uint32_t abs_e; if (f.e < 0) { buf.push_back('-'); abs_e = static_cast(-f.e); } else { buf.push_back('+'); abs_e = static_cast(f.e); } format_decimal(appender(buf), abs_e, detail::count_digits(abs_e)); } template ::value)> FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, buffer& buf) { format_hexfloat(static_cast(value), specs, buf); } constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { // For checking rounding thresholds. // The kth entry is chosen to be the smallest integer such that the // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. // It is equal to ceil(2^31 + 2^32/10^(k + 1)). // These are stored in a string literal because we cannot have static arrays // in constexpr functions and non-static ones are poorly optimized. return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" U"\x800001ae\x8000002b"[index]; } template FMT_CONSTEXPR20 auto format_float(Float value, int precision, const format_specs& specs, bool binary32, buffer& buf) -> int { // float is passed as double to reduce the number of instantiations. static_assert(!std::is_same::value, ""); auto converted_value = convert_float(value); const bool fixed = specs.type() == presentation_type::fixed; if (value == 0) { if (precision <= 0 || !fixed) { buf.push_back('0'); return 0; } buf.try_resize(to_unsigned(precision)); fill_n(buf.data(), precision, '0'); return -precision; } int exp = 0; bool use_dragon = true; unsigned dragon_flags = 0; if (!is_fast_float() || is_constant_evaluated()) { const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) using info = dragonbox::float_info; const auto f = basic_fp(converted_value); // Compute exp, an approximate power of 10, such that // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). // This is based on log10(value) == log2(value) / log2(10) and approximation // of log2(value) by e + num_fraction_bits idea from double-conversion. auto e = (f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10; exp = static_cast(e); if (e > exp) ++exp; // Compute ceil. dragon_flags = dragon::fixup; } else { // Extract significand bits and exponent bits. using info = dragonbox::float_info; auto br = bit_cast(static_cast(value)); const uint64_t significand_mask = (static_cast(1) << num_significand_bits()) - 1; uint64_t significand = (br & significand_mask); int exponent = static_cast((br & exponent_mask()) >> num_significand_bits()); if (exponent != 0) { // Check if normal. exponent -= exponent_bias() + num_significand_bits(); significand |= (static_cast(1) << num_significand_bits()); significand <<= 1; } else { // Normalize subnormal inputs. FMT_ASSERT(significand != 0, "zeros should not appear here"); int shift = countl_zero(significand); FMT_ASSERT(shift >= num_bits() - num_significand_bits(), ""); shift -= (num_bits() - num_significand_bits() - 2); exponent = (std::numeric_limits::min_exponent - num_significand_bits()) - shift; significand <<= shift; } // Compute the first several nonzero decimal significand digits. // We call the number we get the first segment. const int k = info::kappa - dragonbox::floor_log10_pow2(exponent); exp = -k; const int beta = exponent + dragonbox::floor_log2_pow10(k); uint64_t first_segment; bool has_more_segments; int digits_in_the_first_segment; { const auto r = dragonbox::umul192_upper128( significand << beta, dragonbox::get_cached_power(k)); first_segment = r.high(); has_more_segments = r.low() != 0; // The first segment can have 18 ~ 19 digits. if (first_segment >= 1000000000000000000ULL) { digits_in_the_first_segment = 19; } else { // When it is of 18-digits, we align it to 19-digits by adding a bogus // zero at the end. digits_in_the_first_segment = 18; first_segment *= 10; } } // Compute the actual number of decimal digits to print. if (fixed) adjust_precision(precision, exp + digits_in_the_first_segment); // Use Dragon4 only when there might be not enough digits in the first // segment. if (digits_in_the_first_segment > precision) { use_dragon = false; if (precision <= 0) { exp += digits_in_the_first_segment; if (precision < 0) { // Nothing to do, since all we have are just leading zeros. buf.try_resize(0); } else { // We may need to round-up. buf.try_resize(1); if ((first_segment | static_cast(has_more_segments)) > 5000000000000000000ULL) { buf[0] = '1'; } else { buf[0] = '0'; } } } // precision <= 0 else { exp += digits_in_the_first_segment - precision; // When precision > 0, we divide the first segment into three // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits // in 32-bits which usually allows faster calculation than in // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize // division-by-constant for large 64-bit divisors, we do it here // manually. The magic number 7922816251426433760 below is equal to // ceil(2^(64+32) / 10^10). const uint32_t first_subsegment = static_cast( dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >> 32); const uint64_t second_third_subsegments = first_segment - first_subsegment * 10000000000ULL; uint64_t prod; uint32_t digits; bool should_round_up; int number_of_digits_to_print = min_of(precision, 9); // Print a 9-digits subsegment, either the first or the second. auto print_subsegment = [&](uint32_t subsegment, char* buffer) { int number_of_digits_printed = 0; // If we want to print an odd number of digits from the subsegment, if ((number_of_digits_to_print & 1) != 0) { // Convert to 64-bit fixed-point fractional form with 1-digit // integer part. The magic number 720575941 is a good enough // approximation of 2^(32 + 24) / 10^8; see // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case // for details. prod = ((subsegment * static_cast(720575941)) >> 24) + 1; digits = static_cast(prod >> 32); *buffer = static_cast('0' + digits); number_of_digits_printed++; } // If we want to print an even number of digits from the // first_subsegment, else { // Convert to 64-bit fixed-point fractional form with 2-digits // integer part. The magic number 450359963 is a good enough // approximation of 2^(32 + 20) / 10^7; see // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case // for details. prod = ((subsegment * static_cast(450359963)) >> 20) + 1; digits = static_cast(prod >> 32); write2digits(buffer, digits); number_of_digits_printed += 2; } // Print all digit pairs. while (number_of_digits_printed < number_of_digits_to_print) { prod = static_cast(prod) * static_cast(100); digits = static_cast(prod >> 32); write2digits(buffer + number_of_digits_printed, digits); number_of_digits_printed += 2; } }; // Print first subsegment. print_subsegment(first_subsegment, buf.data()); // Perform rounding if the first subsegment is the last subsegment to // print. if (precision <= 9) { // Rounding inside the subsegment. // We round-up if: // - either the fractional part is strictly larger than 1/2, or // - the fractional part is exactly 1/2 and the last digit is odd. // We rely on the following observations: // - If fractional_part >= threshold, then the fractional part is // strictly larger than 1/2. // - If the MSB of fractional_part is set, then the fractional part // must be at least 1/2. // - When the MSB of fractional_part is set, either // second_third_subsegments being nonzero or has_more_segments // being true means there are further digits not printed, so the // fractional part is strictly larger than 1/2. if (precision < 9) { uint32_t fractional_part = static_cast(prod); should_round_up = fractional_part >= fractional_part_rounding_thresholds( 8 - number_of_digits_to_print) || ((fractional_part >> 31) & ((digits & 1) | (second_third_subsegments != 0) | has_more_segments)) != 0; } // Rounding at the subsegment boundary. // In this case, the fractional part is at least 1/2 if and only if // second_third_subsegments >= 5000000000ULL, and is strictly larger // than 1/2 if we further have either second_third_subsegments > // 5000000000ULL or has_more_segments == true. else { should_round_up = second_third_subsegments > 5000000000ULL || (second_third_subsegments == 5000000000ULL && ((digits & 1) != 0 || has_more_segments)); } } // Otherwise, print the second subsegment. else { // Compilers are not aware of how to leverage the maximum value of // second_third_subsegments to find out a better magic number which // allows us to eliminate an additional shift. 1844674407370955162 = // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))). const uint32_t second_subsegment = static_cast(dragonbox::umul128_upper64( second_third_subsegments, 1844674407370955162ULL)); const uint32_t third_subsegment = static_cast(second_third_subsegments) - second_subsegment * 10; number_of_digits_to_print = precision - 9; print_subsegment(second_subsegment, buf.data() + 9); // Rounding inside the subsegment. if (precision < 18) { // The condition third_subsegment != 0 implies that the segment was // of 19 digits, so in this case the third segment should be // consisting of a genuine digit from the input. uint32_t fractional_part = static_cast(prod); should_round_up = fractional_part >= fractional_part_rounding_thresholds( 8 - number_of_digits_to_print) || ((fractional_part >> 31) & ((digits & 1) | (third_subsegment != 0) | has_more_segments)) != 0; } // Rounding at the subsegment boundary. else { // In this case, the segment must be of 19 digits, thus // the third subsegment should be consisting of a genuine digit from // the input. should_round_up = third_subsegment > 5 || (third_subsegment == 5 && ((digits & 1) != 0 || has_more_segments)); } } // Round-up if necessary. if (should_round_up) { ++buf[precision - 1]; for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) { buf[i] = '0'; ++buf[i - 1]; } if (buf[0] > '9') { buf[0] = '1'; if (fixed) buf[precision++] = '0'; else ++exp; } } buf.try_resize(to_unsigned(precision)); } } // if (digits_in_the_first_segment > precision) else { // Adjust the exponent for its use in Dragon4. exp += digits_in_the_first_segment - 1; } } if (use_dragon) { auto f = basic_fp(); bool is_predecessor_closer = binary32 ? f.assign(static_cast(value)) : f.assign(converted_value); if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer; if (fixed) dragon_flags |= dragon::fixed; // 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; format_dragon(f, dragon_flags, precision, buf, exp); } if (!fixed && !specs.alt()) { // 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; } template ::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, locale_ref loc = {}) -> OutputIt { if (specs.localized() && write_loc(out, value, specs, loc)) return out; // Use signbit because value < 0 is false for NaN. sign s = detail::signbit(value) ? sign::minus : specs.sign(); if (!detail::isfinite(value)) return write_nonfinite(out, detail::isnan(value), specs, s); if (specs.align() == align::numeric && s != sign::none) { *out++ = detail::getsign(s); s = sign::none; if (specs.width != 0) --specs.width; } const int exp_upper = detail::exp_upper(); int precision = specs.precision; if (precision < 0) { if (specs.type() != presentation_type::none) { precision = 6; } else if (is_fast_float::value && !is_constant_evaluated()) { // Use Dragonbox for the shortest format. auto dec = dragonbox::to_decimal(static_cast>(value)); return write_float(out, dec, specs, s, exp_upper, loc); } } memory_buffer buffer; if (specs.type() == presentation_type::hexfloat) { if (s != sign::none) buffer.push_back(detail::getsign(s)); format_hexfloat(convert_float(value), specs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } if (specs.type() == presentation_type::exp) { if (precision == max_value()) report_error("number is too big"); else ++precision; if (specs.precision != 0) specs.set_alt(); } else if (specs.type() == presentation_type::fixed) { if (specs.precision != 0) specs.set_alt(); } else if (precision == 0) { precision = 1; } int exp = format_float(convert_float(value), precision, specs, std::is_same(), buffer); specs.precision = precision; auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; return write_float(out, f, specs, s, exp_upper, loc); } template ::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { if (is_constant_evaluated()) return write(out, value, format_specs()); auto s = detail::signbit(value) ? sign::minus : sign::none; auto mask = exponent_mask>(); if ((bit_cast(value) & mask) == mask) return write_nonfinite(out, std::isnan(value), {}, s); auto dec = dragonbox::to_decimal(static_cast>(value)); auto significand = dec.significand; int significand_size = count_digits(significand); int exponent = dec.exponent + significand_size - 1; if (use_fixed(exponent, detail::exp_upper())) { return write_fixed>( out, dec, significand_size, Char('.'), {}, s); } // Write value in the exponential format. const char* prefix = "e+"; int abs_exponent = exponent; if (exponent < 0) { abs_exponent = -exponent; prefix = "e-"; } auto has_decimal_point = significand_size != 1; size_t size = std::is_pointer::value ? 0u : to_unsigned((s != sign::none ? 1 : 0) + significand_size + (has_decimal_point ? 1 : 0) + (abs_exponent >= 100 ? 5 : 4)); if (auto ptr = to_pointer(out, size)) { if (s != sign::none) *ptr++ = Char('-'); if (has_decimal_point) { auto begin = ptr; ptr = format_decimal(ptr, significand, significand_size + 1); *begin = begin[1]; begin[1] = '.'; } else { *ptr++ = static_cast('0' + significand); } if (std::is_same::value) { memcpy(ptr, prefix, 2); ptr += 2; } else { *ptr++ = prefix[0]; *ptr++ = prefix[1]; } if (abs_exponent >= 100) { *ptr++ = static_cast('0' + abs_exponent / 100); abs_exponent %= 100; } write2digits(ptr, static_cast(abs_exponent)); return select::value>(ptr + 2, out); } auto it = reserve(out, size); if (s != sign::none) *it++ = Char('-'); // Insert a decimal point after the first digit and add an exponent. it = write_significand(it, significand, significand_size, 1, has_decimal_point ? Char('.') : Char()); *it++ = Char('e'); it = write_exponent(exponent, it); return base_iterator(out, it); } template ::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { return write(out, value, {}); } template auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) -> OutputIt { FMT_ASSERT(false, ""); return out; } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) -> OutputIt { return copy_noinline(value.begin(), value.end(), out); } template ::value)> constexpr auto write(OutputIt out, const T& value) -> OutputIt { return write(out, to_string_view(value)); } // FMT_ENABLE_IF() condition separated to workaround an 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>(value)); } template ::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, const format_specs& specs = {}, locale_ref = {}) -> OutputIt { return specs.type() != presentation_type::none && specs.type() != presentation_type::string ? 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_CONSTEXPR20 auto write(OutputIt out, const Char* value) -> OutputIt { if (value) return write(out, basic_string_view(value)); report_error("string pointer is null"); return out; } template ::value)> auto write(OutputIt out, const T* value, const format_specs& specs = {}, locale_ref = {}) -> OutputIt { return write_ptr(out, bit_cast(value), &specs); } template ::value == type::custom_type && !std::is_fundamental::value)> FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> OutputIt { auto f = formatter(); auto parse_ctx = parse_context({}); f.parse(parse_ctx); auto ctx = basic_format_context(out, {}, {}); return f.format(value, ctx); } template using is_builtin = bool_constant::value || FMT_BUILTIN_TYPES>; // 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 context = buffered_context; basic_appender out; void operator()(monostate) { report_error("argument not found"); } template ::value)> void operator()(T value) { write(out, value); } template ::value)> void operator()(T) { FMT_ASSERT(false, ""); } void operator()(typename basic_format_arg::handle h) { // Use a null locale since the default format must be unlocalized. auto parse_ctx = parse_context({}); auto format_ctx = context(out, {}, {}); h.format(parse_ctx, format_ctx); } }; template struct arg_formatter { basic_appender out; const format_specs& specs; FMT_NO_UNIQUE_ADDRESS locale_ref locale; template ::value)> FMT_CONSTEXPR FMT_INLINE void operator()(T value) { detail::write(out, value, specs, locale); } template ::value)> void operator()(T) { FMT_ASSERT(false, ""); } void operator()(typename basic_format_arg>::handle) { // User-defined types are handled separately because they require access // to the parse context. } }; struct dynamic_spec_getter { template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { return is_negative(value) ? ~0ull : static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { report_error("width/precision is not integer"); return 0; } }; template FMT_CONSTEXPR void handle_dynamic_spec( arg_id_kind kind, int& value, const arg_ref& ref, Context& ctx) { if (kind == arg_id_kind::none) return; auto arg = kind == arg_id_kind::index ? ctx.arg(ref.index) : ctx.arg(ref.name); if (!arg) report_error("argument not found"); unsigned long long result = arg.visit(dynamic_spec_getter()); if (result > to_unsigned(max_value())) report_error("width/precision is out of range"); value = static_cast(result); } #if FMT_USE_NONTYPE_TEMPLATE_ARGS template Str> struct static_named_arg : view { static constexpr auto name = Str.data; const T& value; static_named_arg(const T& v) : value(v) {} }; template Str> struct is_named_arg> : std::true_type {}; template Str> struct is_static_named_arg> : std::true_type { }; template Str> struct udl_arg { template auto operator=(T&& value) const { return static_named_arg(std::forward(value)); } }; #else template struct udl_arg { const Char* str; template auto operator=(T&& value) const -> named_arg { return {str, std::forward(value)}; } }; #endif // FMT_USE_NONTYPE_TEMPLATE_ARGS template struct format_handler { parse_context parse_ctx; buffered_context ctx; void on_text(const Char* begin, const Char* end) { copy_noinline(begin, end, ctx.out()); } FMT_CONSTEXPR auto on_arg_id() -> int { return parse_ctx.next_arg_id(); } FMT_CONSTEXPR auto on_arg_id(int id) -> int { parse_ctx.check_arg_id(id); return id; } FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { parse_ctx.check_arg_id(id); int arg_id = ctx.arg_id(id); if (arg_id < 0) report_error("argument not found"); return arg_id; } FMT_INLINE void on_replacement_field(int id, const Char*) { ctx.arg(id).visit(default_arg_formatter{ctx.out()}); } auto on_format_specs(int id, const Char* begin, const Char* end) -> const Char* { auto arg = ctx.arg(id); if (!arg) report_error("argument not found"); // Not using a visitor for custom types gives better codegen. if (arg.format_custom(begin, parse_ctx, ctx)) return parse_ctx.begin(); auto specs = dynamic_format_specs(); begin = parse_format_specs(begin, end, specs, parse_ctx, arg.type()); if (specs.dynamic()) { handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, ctx); handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs.precision_ref, ctx); } arg.visit(arg_formatter{ctx.out(), specs, ctx.locale()}); return begin; } FMT_NORETURN void on_error(const char* message) { report_error(message); } }; // It is used in format-inl.h and os.cc. using format_func = void (*)(detail::buffer&, int, const char*); FMT_API void do_report_error(format_func func, int error_code, const char* message) noexcept; FMT_API void format_error_code(buffer& out, int error_code, string_view message) noexcept; template template FMT_CONSTEXPR auto native_formatter::format( const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { if (!specs_.dynamic()) return write(ctx.out(), val, specs_, ctx.locale()); auto specs = format_specs(specs_); handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref, ctx); handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs_.precision_ref, ctx); return write(ctx.out(), val, specs, ctx.locale()); } } // namespace detail FMT_BEGIN_EXPORT // A generic formatting context with custom output iterator and character // (code unit) support. Char is the format string code unit type which can be // different from OutputIt::value_type. template class generic_context { private: OutputIt out_; basic_format_args args_; locale_ref loc_; public: using char_type = Char; using iterator = OutputIt; enum { builtin_types = FMT_BUILTIN_TYPES }; constexpr generic_context(OutputIt out, basic_format_args args, locale_ref loc = {}) : out_(out), args_(args), loc_(loc) {} generic_context(generic_context&&) = default; generic_context(const generic_context&) = delete; void operator=(const generic_context&) = delete; constexpr auto arg(int id) const -> basic_format_arg { return args_.get(id); } auto arg(basic_string_view name) const -> basic_format_arg { return args_.get(name); } constexpr auto arg_id(basic_string_view name) const -> int { return args_.get_id(name); } constexpr auto out() const -> iterator { return out_; } void advance_to(iterator it) { if (!detail::is_back_insert_iterator()) out_ = it; } constexpr auto locale() const -> locale_ref { return loc_; } }; class loc_value { private: basic_format_arg value_; public: template ::value)> loc_value(T value) : value_(value) {} template ::value)> loc_value(T) {} template auto visit(Visitor&& vis) -> decltype(vis(0)) { return value_.visit(vis); } }; // A locale facet that formats values in UTF-8. // It is parameterized on the locale to avoid the heavy include. template class format_facet : public Locale::facet { private: std::string separator_; std::string grouping_; std::string decimal_point_; protected: virtual auto do_put(appender out, loc_value val, const format_specs& specs) const -> bool; public: static FMT_API typename Locale::id id; explicit format_facet(Locale& loc); explicit format_facet(string_view sep = "", std::string grouping = "\3", std::string decimal_point = ".") : separator_(sep.data(), sep.size()), grouping_(grouping), decimal_point_(decimal_point) {} auto put(appender out, loc_value val, const format_specs& specs) const -> bool { return do_put(out, val, specs); } }; #define FMT_FORMAT_AS(Type, Base) \ template \ struct formatter : formatter { \ template \ FMT_CONSTEXPR auto format(Type value, FormatContext& ctx) const \ -> decltype(ctx.out()) { \ return formatter::format(value, ctx); \ } \ } FMT_FORMAT_AS(signed char, int); FMT_FORMAT_AS(unsigned char, unsigned); FMT_FORMAT_AS(short, int); FMT_FORMAT_AS(unsigned short, unsigned); FMT_FORMAT_AS(long, detail::long_type); FMT_FORMAT_AS(unsigned long, detail::ulong_type); FMT_FORMAT_AS(Char*, const Char*); FMT_FORMAT_AS(detail::std_string_view, basic_string_view); FMT_FORMAT_AS(std::nullptr_t, const void*); FMT_FORMAT_AS(void*, const void*); template struct formatter : formatter, Char> {}; template class formatter, Char> : public formatter, Char> {}; template struct formatter, Char> : formatter {}; template struct formatter, Char> : formatter {}; template struct formatter : detail::native_formatter {}; template struct formatter>> : formatter, Char> { template FMT_CONSTEXPR auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) { auto&& val = format_as(value); // Make an lvalue reference for format. return formatter, Char>::format(val, ctx); } }; /** * Converts `p` to `const void*` for pointer formatting. * * **Example**: * * auto s = fmt::format("{}", fmt::ptr(p)); */ template auto ptr(T p) -> const void* { static_assert(std::is_pointer::value, "fmt::ptr used with non-pointer"); return detail::bit_cast(p); } /** * Converts `e` to the underlying type. * * **Example**: * * enum class color { red, green, blue }; * auto s = fmt::format("{}", fmt::underlying(color::red)); // s == "0" */ template constexpr auto underlying(Enum e) noexcept -> underlying_t { return static_cast>(e); } namespace enums { template ::value)> constexpr auto format_as(Enum e) noexcept -> underlying_t { return static_cast>(e); } } // namespace enums #ifdef __cpp_lib_byte template struct formatter : formatter { static auto format_as(std::byte b) -> unsigned char { return static_cast(b); } template auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) { return formatter::format(format_as(b), ctx); } }; #endif struct bytes { string_view data; inline explicit bytes(string_view s) : data(s) {} }; template <> struct formatter { private: detail::dynamic_format_specs<> specs_; public: FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, detail::type::string_type); } template auto format(bytes b, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs.precision_ref, ctx); return detail::write_bytes(ctx.out(), b.data, specs); } }; // group_digits_view is not derived from view because it copies the argument. template struct group_digits_view { T value; }; /** * Returns a view that formats an integer value using ',' as a * locale-independent thousands separator. * * **Example**: * * fmt::print("{}", fmt::group_digits(12345)); * // Output: "12,345" */ template auto group_digits(T value) -> group_digits_view { return {value}; } template struct formatter> : formatter { private: detail::dynamic_format_specs<> specs_; public: FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, detail::type::int_type); } template auto format(group_digits_view view, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs.precision_ref, ctx); auto arg = detail::make_write_int_arg(view.value, specs.sign()); return detail::write_int( ctx.out(), static_cast>(arg.abs_value), arg.prefix, specs, detail::digit_grouping("\3", ",")); } }; template struct nested_view { const formatter* fmt; const T* value; }; template struct formatter, Char> { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return ctx.begin(); } template auto format(nested_view view, FormatContext& ctx) const -> decltype(ctx.out()) { return view.fmt->format(*view.value, ctx); } }; template struct nested_formatter { private: basic_specs specs_; int width_; formatter formatter_; public: constexpr nested_formatter() : width_(0) {} FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it == end) return it; auto specs = format_specs(); it = detail::parse_align(it, end, specs); specs_ = specs; Char c = *it; auto width_ref = detail::arg_ref(); if ((c >= '0' && c <= '9') || c == '{') { it = detail::parse_width(it, end, specs, width_ref, ctx); width_ = specs.width; } ctx.advance_to(it); return formatter_.parse(ctx); } template auto write_padded(FormatContext& ctx, F write) const -> decltype(ctx.out()) { if (width_ == 0) return write(ctx.out()); auto buf = basic_memory_buffer(); write(basic_appender(buf)); auto specs = format_specs(); specs.width = width_; specs.copy_fill_from(specs_); specs.set_align(specs_.align()); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } auto nested(const T& value) const -> nested_view { return nested_view{&formatter_, &value}; } }; inline namespace literals { #if FMT_USE_NONTYPE_TEMPLATE_ARGS template constexpr auto operator""_a() { using char_t = remove_cvref_t; return detail::udl_arg(); } #else /** * User-defined literal equivalent of `fmt::arg`. * * **Example**: * * using namespace fmt::literals; * fmt::print("The answer is {answer}.", "answer"_a=42); */ constexpr auto operator""_a(const char* s, size_t) -> detail::udl_arg { return {s}; } #endif // FMT_USE_NONTYPE_TEMPLATE_ARGS } // namespace literals /// A fast integer formatter. class format_int { private: // Buffer should be large enough to hold all digits (digits10 + 1), // a sign and a null character. enum { buffer_size = std::numeric_limits::digits10 + 3 }; mutable char buffer_[buffer_size]; char* str_; template FMT_CONSTEXPR20 auto format_unsigned(UInt value) -> char* { auto n = static_cast>(value); return detail::do_format_decimal(buffer_, n, buffer_size - 1); } template FMT_CONSTEXPR20 auto format_signed(Int value) -> char* { auto abs_value = static_cast>(value); bool negative = value < 0; if (negative) abs_value = 0 - abs_value; auto begin = format_unsigned(abs_value); if (negative) *--begin = '-'; return begin; } public: FMT_CONSTEXPR20 explicit format_int(int value) : str_(format_signed(value)) {} FMT_CONSTEXPR20 explicit format_int(long value) : str_(format_signed(value)) {} FMT_CONSTEXPR20 explicit format_int(long long value) : str_(format_signed(value)) {} FMT_CONSTEXPR20 explicit format_int(unsigned value) : str_(format_unsigned(value)) {} FMT_CONSTEXPR20 explicit format_int(unsigned long value) : str_(format_unsigned(value)) {} FMT_CONSTEXPR20 explicit format_int(unsigned long long value) : str_(format_unsigned(value)) {} /// Returns the number of characters written to the output buffer. FMT_CONSTEXPR20 auto size() const -> size_t { return detail::to_unsigned(buffer_ - str_ + buffer_size - 1); } /// Returns a pointer to the output buffer content. No terminating null /// character is appended. FMT_CONSTEXPR20 auto data() const -> const char* { return str_; } /// Returns a pointer to the output buffer content with terminating null /// character appended. FMT_CONSTEXPR20 auto c_str() const -> const char* { buffer_[buffer_size - 1] = '\0'; return str_; } /// Returns the content of the output buffer as an `std::string`. inline auto str() const -> std::string { return {str_, size()}; } }; #if FMT_CLANG_ANALYZER # define FMT_STRING_IMPL(s, base) s #else # define FMT_STRING_IMPL(s, base) \ [] { \ /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ /* Use a macro-like name to avoid shadowing warnings. */ \ struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ using char_type = fmt::remove_cvref_t; \ constexpr explicit operator fmt::basic_string_view() \ const { \ return fmt::detail::compile_string_to_view(s); \ } \ }; \ using FMT_STRING_VIEW = \ fmt::basic_string_view; \ fmt::detail::ignore_unused(FMT_STRING_VIEW(FMT_COMPILE_STRING())); \ return FMT_COMPILE_STRING(); \ }() #endif // FMT_CLANG_ANALYZER /** * Constructs a legacy compile-time format string from a string literal `s`. * * **Example**: * * // A compile-time error because 'd' is an invalid specifier for strings. * std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); */ #define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string) FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args) -> std::system_error; /** * Constructs `std::system_error` with a message formatted with * `fmt::format(fmt, args...)`. * `error_code` is a system error code as given by `errno`. * * **Example**: * * // This throws std::system_error with the description * // cannot open file 'madeup': No such file or directory * // or similar (system message may vary). * const char* filename = "madeup"; * FILE* file = fopen(filename, "r"); * if (!file) * throw fmt::system_error(errno, "cannot open file '{}'", filename); */ template auto system_error(int error_code, format_string fmt, T&&... args) -> std::system_error { return vsystem_error(error_code, fmt.str, vargs{{args...}}); } /** * Formats an error message for an error returned by an operating system or a * language runtime, for example a file opening error, and writes it to `out`. * The format is the same as the one used by `std::system_error(ec, message)` * where `ec` is `std::error_code(error_code, std::generic_category())`. * It is implementation-defined but normally looks like: * * : * * where `` is the passed message and `` is the system * message corresponding to the error code. * `error_code` is a system error code as given by `errno`. */ FMT_API void format_system_error(detail::buffer& out, int error_code, const char* message) noexcept; // Reports a system error without throwing an exception. // Can be used to report errors from destructors. FMT_API void report_system_error(int error_code, const char* message) noexcept; inline auto vformat(locale_ref loc, string_view fmt, format_args args) -> std::string { auto buf = memory_buffer(); detail::vformat_to(buf, fmt, args, loc); return {buf.data(), buf.size()}; } template FMT_INLINE auto format(locale_ref loc, format_string fmt, T&&... args) -> std::string { return vformat(loc, fmt.str, vargs{{args...}}); } template ::value)> auto vformat_to(OutputIt out, locale_ref loc, string_view fmt, format_args args) -> OutputIt { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, fmt, args, loc); return detail::get_iterator(buf, out); } template ::value)> FMT_INLINE auto format_to(OutputIt out, locale_ref loc, format_string fmt, T&&... args) -> OutputIt { return fmt::vformat_to(out, loc, fmt.str, vargs{{args...}}); } template FMT_NODISCARD FMT_INLINE auto formatted_size(locale_ref loc, format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); detail::vformat_to(buf, fmt.str, vargs{{args...}}, loc); return buf.count(); } FMT_API auto vformat(string_view fmt, format_args args) -> std::string; /** * 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); */ template FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) -> std::string { return vformat(fmt.str, vargs{{args...}}); } /** * Converts `value` to `std::string` using the default format for type `T`. * * **Example**: * * std::string answer = fmt::to_string(42); */ template ::value)> FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(T value) -> std::string { // The buffer should be large enough to store the number including the sign // or "false" for bool. char buffer[max_of(detail::digits10() + 2, 5)]; return {buffer, detail::write(buffer, value)}; } template ::value)> FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value) -> std::string { return to_string(format_as(value)); } template ::value && !detail::use_format_as::value)> FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value) -> std::string { auto buffer = memory_buffer(); detail::write(appender(buffer), value); return {buffer.data(), buffer.size()}; } FMT_END_EXPORT FMT_END_NAMESPACE #ifdef FMT_HEADER_ONLY # define FMT_FUNC inline # include "format-inl.h" #endif // Restore _LIBCPP_REMOVE_TRANSITIVE_INCLUDES. #ifdef FMT_REMOVE_TRANSITIVE_INCLUDES # undef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES #endif #endif // FMT_FORMAT_H_ pavel-odintsov-fastnetmon-394fbe0/src/gobgp_client/000077500000000000000000000000001520703010000224655ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/gobgp_client/attribute.proto000066400000000000000000000520771520703010000255700ustar00rootroot00000000000000// Copyright (C) 2018 Nippon Telegraph and Telephone Corporation. // // 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. syntax = "proto3"; package apipb; option go_package = "github.com/osrg/gobgp/v3/api;apipb"; import "google/protobuf/any.proto"; import "gobgp.proto"; message OriginAttribute { uint32 origin = 1; } message AsSegment { enum Type { UNKNOWN = 0; AS_SET = 1; AS_SEQUENCE = 2; AS_CONFED_SEQUENCE = 3; AS_CONFED_SET = 4; } Type type = 1; repeated uint32 numbers = 2; } message AsPathAttribute { repeated AsSegment segments = 1; } message NextHopAttribute { string next_hop = 1; } message MultiExitDiscAttribute { uint32 med = 1; } message LocalPrefAttribute { uint32 local_pref = 1; } message AtomicAggregateAttribute {} message AggregatorAttribute { uint32 asn = 1; string address = 2; } message CommunitiesAttribute { repeated uint32 communities = 1; } message OriginatorIdAttribute { string id = 1; } message ClusterListAttribute { repeated string ids = 1; } // IPAddressPrefix represents the NLRI for: // - AFI=1, SAFI=1 // - AFI=2, SAFI=1 message IPAddressPrefix { uint32 prefix_len = 1; string prefix = 2; } // LabeledIPAddressPrefix represents the NLRI for: // - AFI=1, SAFI=4 // - AFI=2, SAFI=4 message LabeledIPAddressPrefix { repeated uint32 labels = 1; uint32 prefix_len = 2; string prefix = 3; } // EncapsulationNLRI represents the NLRI for: // - AFI=1, SAFI=7 // - AFI=2, SAFI=7 message EncapsulationNLRI { string address = 1; } message RouteDistinguisherTwoOctetASN { uint32 admin = 1; uint32 assigned = 2; } message RouteDistinguisherIPAddress { string admin = 1; uint32 assigned = 2; } message RouteDistinguisherFourOctetASN { uint32 admin = 1; uint32 assigned = 2; } message EthernetSegmentIdentifier { uint32 type = 1; bytes value = 2; } // EVPNEthernetAutoDiscoveryRoute represents the NLRI for: // - AFI=25, SAFI=70, RouteType=1 message EVPNEthernetAutoDiscoveryRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; EthernetSegmentIdentifier esi = 2; uint32 ethernet_tag = 3; uint32 label = 4; } // EVPNMACIPAdvertisementRoute represents the NLRI for: // - AFI=25, SAFI=70, RouteType=2 message EVPNMACIPAdvertisementRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; EthernetSegmentIdentifier esi = 2; uint32 ethernet_tag = 3; string mac_address = 4; string ip_address = 5; repeated uint32 labels = 6; } // EVPNInclusiveMulticastEthernetTagRoute represents the NLRI for: // - AFI=25, SAFI=70, RouteType=3 message EVPNInclusiveMulticastEthernetTagRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; uint32 ethernet_tag = 2; string ip_address = 3; } // EVPNEthernetSegmentRoute represents the NLRI for: // - AFI=25, SAFI=70, RouteType=4 message EVPNEthernetSegmentRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; EthernetSegmentIdentifier esi = 2; string ip_address = 3; } // EVPNIPPrefixRoute represents the NLRI for: // - AFI=25, SAFI=70, RouteType=5 message EVPNIPPrefixRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; EthernetSegmentIdentifier esi = 2; uint32 ethernet_tag = 3; string ip_prefix = 4; uint32 ip_prefix_len = 5; string gw_address = 6; uint32 label = 7; } // EVPNIPMSIRoute represents the NLRI for: // - AFI=25, SAFI=70, RouteType=9 message EVPNIPMSIRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; uint32 ethernet_tag = 2; google.protobuf.Any rt = 3; } // SRPolicyNLRI represents the NLRI for: // - AFI=1, SAFI=73 // - AFI=2, SAFI=73 message SRPolicyNLRI { // length field carries the length of NLRI portion expressed in bits uint32 length = 1; // distinguisher field carries 4-octet value uniquely identifying the policy // in the context of tuple. uint32 distinguisher = 2; // color field carries 4-octet value identifying (with the endpoint) the // policy. The color is used to match the color of the destination // prefixes to steer traffic into the SR Policy uint32 color = 3; // endpoint field identifies the endpoint of a policy. The Endpoint may // represent a single node or a set of nodes (e.g., an anycast // address). The Endpoint is an IPv4 (4-octet) address or an IPv6 // (16-octet) address according to the AFI of the NLRI. bytes endpoint = 4; } // LabeledVPNIPAddressPrefix represents the NLRI for: // - AFI=1, SAFI=128 // - AFI=2, SAFI=128 message LabeledVPNIPAddressPrefix { repeated uint32 labels = 1; // One of: // - TwoOctetAsSpecificExtended // - IPv4AddressSpecificExtended // - FourOctetAsSpecificExtended google.protobuf.Any rd = 2; uint32 prefix_len = 3; string prefix = 4; } // RouteTargetMembershipNLRI represents the NLRI for: // - AFI=1, SAFI=132 message RouteTargetMembershipNLRI { uint32 asn = 1; // One of: // - TwoOctetAsSpecificExtended // - IPv4AddressSpecificExtended // - FourOctetAsSpecificExtended google.protobuf.Any rt = 2; } message FlowSpecIPPrefix { uint32 type = 1; uint32 prefix_len = 2; string prefix = 3; // IPv6 only uint32 offset = 4; } message FlowSpecMAC { uint32 type = 1; string address = 2; } message FlowSpecComponentItem { // Operator for Numeric type, Operand for Bitmask type uint32 op = 1; uint64 value = 2; } message FlowSpecComponent { uint32 type = 1; repeated FlowSpecComponentItem items = 2; } // FlowSpecNLRI represents the NLRI for: // - AFI=1, SAFI=133 // - AFI=2, SAFI=133 message FlowSpecNLRI { // One of: // - FlowSpecIPPrefix // - FlowSpecMAC // - FlowSpecComponent repeated google.protobuf.Any rules = 1; } // VPNFlowSpecNLRI represents the NLRI for: // - AFI=1, SAFI=134 // - AFI=2, SAFI=134 // - AFI=25, SAFI=134 message VPNFlowSpecNLRI { // One of: // - RouteDistinguisherTwoOctetAS // - RouteDistinguisherIPAddressAS // - RouteDistinguisherFourOctetAS google.protobuf.Any rd = 1; // One of: // - FlowSpecIPPrefix // - FlowSpecMAC // - FlowSpecComponent repeated google.protobuf.Any rules = 2; } // OpaqueNLRI represents the NLRI for: // - AFI=16397, SAFI=241 message OpaqueNLRI { bytes key = 1; bytes value = 2; } message LsNodeDescriptor { uint32 asn = 1; uint32 bgp_ls_id = 2; uint32 ospf_area_id = 3; bool pseudonode = 4; string igp_router_id = 5; string bgp_router_id = 6; uint32 bgp_confederation_member = 7; } message LsLinkDescriptor { uint32 link_local_id = 1; uint32 link_remote_id = 2; string interface_addr_ipv4 = 3; string neighbor_addr_ipv4 = 4; string interface_addr_ipv6 = 5; string neighbor_addr_ipv6 = 6; } enum LsOspfRouteType { LS_OSPF_ROUTE_TYPE_UNKNOWN = 0; LS_OSPF_ROUTE_TYPE_INTRA_AREA = 1; LS_OSPF_ROUTE_TYPE_INTER_AREA = 2; LS_OSPF_ROUTE_TYPE_EXTERNAL1 = 3; LS_OSPF_ROUTE_TYPE_EXTERNAL2 = 4; LS_OSPF_ROUTE_TYPE_NSSA1 = 5; LS_OSPF_ROUTE_TYPE_NSSA2 = 6; } message LsPrefixDescriptor { repeated string ip_reachability = 1; LsOspfRouteType ospf_route_type = 2; } message LsNodeNLRI { LsNodeDescriptor local_node = 1; } message LsLinkNLRI { LsNodeDescriptor local_node = 1; LsNodeDescriptor remote_node = 2; LsLinkDescriptor link_descriptor = 3; } message LsPrefixV4NLRI { LsNodeDescriptor local_node = 1; LsPrefixDescriptor prefix_descriptor = 2; } message LsPrefixV6NLRI { LsNodeDescriptor local_node = 1; LsPrefixDescriptor prefix_descriptor = 2; } // Based om RFC 7752, Table 1. enum LsNLRIType { LS_NLRI_UNKNOWN = 0; LS_NLRI_NODE = 1; LS_NLRI_LINK = 2; LS_NLRI_PREFIX_V4 = 3; LS_NLRI_PREFIX_V6 = 4; } enum LsProtocolID { LS_PROTOCOL_UNKNOWN = 0; LS_PROTOCOL_ISIS_L1 = 1; LS_PROTOCOL_ISIS_L2 = 2; LS_PROTOCOL_OSPF_V2 = 3; LS_PROTOCOL_DIRECT = 4; LS_PROTOCOL_STATIC = 5; LS_PROTOCOL_OSPF_V3 = 6; } // LsAddrPrefix represents the NLRI for: // - AFI=16388, SAFI=71 message LsAddrPrefix { LsNLRIType type = 1; // One of: // - LsNodeNLRI // - LsLinkNLRI // - LsPrefixV4NLRI // - LsPrefixV6NLRI google.protobuf.Any nlri = 2; uint32 length = 3; LsProtocolID protocol_id = 4; uint64 identifier = 5; } message MUPInterworkSegmentDiscoveryRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; string prefix = 2; } message MUPDirectSegmentDiscoveryRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; string address = 2; } message MUPType1SessionTransformedRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; uint32 prefix_length = 2 [deprecated = true]; string prefix = 3; uint32 teid = 4; uint32 qfi = 5; uint32 endpoint_address_length = 6; string endpoint_address = 7; } message MUPType2SessionTransformedRoute { // One of: // - RouteDistinguisherTwoOctetASN // - RouteDistinguisherIPAddress // - RouteDistinguisherFourOctetASN google.protobuf.Any rd = 1; uint32 endpoint_address_length = 2; string endpoint_address = 3; uint32 teid = 4; } message MpReachNLRIAttribute { apipb.Family family = 1; repeated string next_hops = 2; // Each NLRI must be one of: // - IPAddressPrefix // - LabeledIPAddressPrefix // - EncapsulationNLRI // - EVPNEthernetAutoDiscoveryRoute // - EVPNMACIPAdvertisementRoute // - EVPNInclusiveMulticastEthernetTagRoute // - EVPNEthernetSegmentRoute // - EVPNIPPrefixRoute // - EVPNIPMSIRoute // - LabeledVPNIPAddressPrefix // - RouteTargetMembershipNLRI // - FlowSpecNLRI // - VPNFlowSpecNLRI // - OpaqueNLRI // - LsAddrPrefix // - SR Policy NLRI // - MUPInterworkSegmentDiscoveryRoute // - MUPDirectSegmentDiscoveryRoute // - MUPType1SessionTransformedRoute // - MUPType2SessionTransformedRoute repeated google.protobuf.Any nlris = 3; } message MpUnreachNLRIAttribute { apipb.Family family = 1; // The same as NLRI field of MpReachNLRIAttribute repeated google.protobuf.Any nlris = 3; } message TwoOctetAsSpecificExtended { bool is_transitive = 1; uint32 sub_type = 2; uint32 asn = 3; uint32 local_admin = 4; } message IPv4AddressSpecificExtended { bool is_transitive = 1; uint32 sub_type = 2; string address = 3; uint32 local_admin = 4; } message FourOctetAsSpecificExtended { bool is_transitive = 1; uint32 sub_type = 2; uint32 asn = 3; uint32 local_admin = 4; } message LinkBandwidthExtended { uint32 asn = 1; float bandwidth = 2; } message ValidationExtended { uint32 state = 1; } message ColorExtended { uint32 color = 1; } message EncapExtended { uint32 tunnel_type = 1; } message DefaultGatewayExtended {} message OpaqueExtended { bool is_transitive = 1; bytes value = 3; } message ESILabelExtended { bool is_single_active = 1; uint32 label = 2; } message ESImportRouteTarget { string es_import = 1; } message MacMobilityExtended { bool is_sticky = 1; uint32 sequence_num = 2; } message RouterMacExtended { string mac = 1; } message TrafficRateExtended { uint32 asn = 1; float rate = 2; } message TrafficActionExtended { bool terminal = 1; bool sample = 2; } message RedirectTwoOctetAsSpecificExtended { uint32 asn = 1; uint32 local_admin = 2; } message RedirectIPv4AddressSpecificExtended { string address = 1; uint32 local_admin = 2; } message RedirectFourOctetAsSpecificExtended { uint32 asn = 1; uint32 local_admin = 2; } message TrafficRemarkExtended { uint32 dscp = 1; } message MUPExtended { uint32 sub_type = 1; uint32 segment_id2 = 2; uint32 segment_id4 = 3; } message UnknownExtended { uint32 type = 1; bytes value = 2; } message ExtendedCommunitiesAttribute { // Each Community must be one of: // - TwoOctetAsSpecificExtended // - IPv4AddressSpecificExtended // - FourOctetAsSpecificExtended // - OpaqueExtended // - ESILabelExtended // - MacMobilityExtended // - RouterMacExtended // - TrafficRateExtended // - TrafficActionExtended // - RedirectTwoOctetAsSpecificExtended // - RedirectIPv4AddressSpecificExtended // - RedirectFourOctetAsSpecificExtended // - TrafficRemarkExtended // - MUPExtended // - UnknownExtended repeated google.protobuf.Any communities = 1; } message As4PathAttribute { repeated AsSegment segments = 1; } message As4AggregatorAttribute { uint32 asn = 2; string address = 3; } message PmsiTunnelAttribute { uint32 flags = 1; uint32 type = 2; uint32 label = 3; bytes id = 4; } message TunnelEncapSubTLVEncapsulation { uint32 key = 1; bytes cookie = 2; } message TunnelEncapSubTLVProtocol { uint32 protocol = 1; } message TunnelEncapSubTLVColor { uint32 color = 1; } message TunnelEncapSubTLVSRPreference { uint32 flags = 1; uint32 preference = 2; } message TunnelEncapSubTLVSRCandidatePathName { string candidate_path_name = 1; } message TunnelEncapSubTLVSRPriority { uint32 priority = 1; } message TunnelEncapSubTLVSRBindingSID { // bsid must be one of: // - SRBindingSID // - SRv6BindingSID google.protobuf.Any bsid = 1; } message SRBindingSID { bool s_flag = 1; bool i_flag = 2; bytes sid = 3; } enum SRv6Behavior { RESERVED = 0; END = 1; END_WITH_PSP = 2; END_WITH_USP = 3; END_WITH_PSP_USP = 4; ENDX = 5; ENDX_WITH_PSP = 6; ENDX_WITH_USP = 7; ENDX_WITH_PSP_USP = 8; ENDT = 9; ENDT_WITH_PSP = 10; ENDT_WITH_USP = 11; ENDT_WITH_PSP_USP = 12; END_B6_ENCAPS = 14; END_BM = 15; END_DX6 = 16; END_DX4 = 17; END_DT6 = 18; END_DT4 = 19; END_DT46 = 20; END_DX2 = 21; END_DX2V = 22; END_DT2U = 23; END_DT2M = 24; END_B6_ENCAPS_Red = 27; END_WITH_USD = 28; END_WITH_PSP_USD = 29; END_WITH_USP_USD = 30; END_WITH_PSP_USP_USD = 31; ENDX_WITH_USD = 32; ENDX_WITH_PSP_USD = 33; ENDX_WITH_USP_USD = 34; ENDX_WITH_PSP_USP_USD = 35; ENDT_WITH_USD = 36; ENDT_WITH_PSP_USD = 37; ENDT_WITH_USP_USD = 38; ENDT_WITH_PSP_USP_USD = 39; ENDM_GTP6D = 69; // 0x0045 ENDM_GTP6DI = 70; // 0x0046 ENDM_GTP6E = 71; // 0x0047 ENDM_GTP4E = 72; // 0x0048 } message SRv6EndPointBehavior { SRv6Behavior behavior = 1; uint32 block_len = 2; uint32 node_len = 3; uint32 func_len = 4; uint32 arg_len = 5; } message SRv6BindingSID { bool s_flag = 1; bool i_flag = 2; bool b_flag = 3; bytes sid = 4; SRv6EndPointBehavior endpoint_behavior_structure = 5; } enum ENLPType { Reserved = 0; Type1 = 1; Type2 = 2; Type3 = 3; Type4 = 4; } message TunnelEncapSubTLVSRENLP { uint32 flags = 1; ENLPType enlp = 2; } message SRWeight { uint32 flags = 1; uint32 weight = 2; } message SegmentFlags { bool v_flag = 1; bool a_flag = 2; bool s_flag = 3; bool b_flag = 4; } message SegmentTypeA { SegmentFlags flags = 1; uint32 label = 2; } message SegmentTypeB { SegmentFlags flags = 1; bytes sid = 2; SRv6EndPointBehavior endpoint_behavior_structure = 3; } message TunnelEncapSubTLVSRSegmentList { SRWeight weight = 1; // segments must be one of: // - SegmentTypeA // - SegmentTypeB repeated google.protobuf.Any segments = 2; } message TunnelEncapSubTLVEgressEndpoint { string address = 1; } message TunnelEncapSubTLVUDPDestPort { uint32 port = 1; } message TunnelEncapSubTLVUnknown { uint32 type = 1; bytes value = 2; } message TunnelEncapTLV { uint32 type = 1; // Each TLV must be one of: // - TunnelEncapSubTLVEncapsulation // - TunnelEncapSubTLVProtocol // - TunnelEncapSubTLVColor // - TunnelEncapSubTLVSRPolicy // - TunnelEncapSubTLVUnknown repeated google.protobuf.Any tlvs = 2; } message TunnelEncapAttribute { repeated TunnelEncapTLV tlvs = 1; } message IPv6AddressSpecificExtended { bool is_transitive = 1; uint32 sub_type = 2; string address = 3; uint32 local_admin = 4; } message RedirectIPv6AddressSpecificExtended { string address = 1; uint32 local_admin = 2; } message IP6ExtendedCommunitiesAttribute { // Each Community must be one of: // - IPv6AddressSpecificExtended // - RedirectIPv6AddressSpecificExtended repeated google.protobuf.Any communities = 1; } message AigpTLVIGPMetric { uint64 metric = 1; } message AigpTLVUnknown { uint32 type = 1; bytes value = 2; } message AigpAttribute { // Each TLV must be one of: // - AigpTLVIGPMetric // - AigpTLVUnknown repeated google.protobuf.Any tlvs = 1; } message LargeCommunity { uint32 global_admin = 1; uint32 local_data1 = 2; uint32 local_data2 = 3; } message LargeCommunitiesAttribute { repeated LargeCommunity communities = 1; } message LsNodeFlags { bool overload = 1; bool attached = 2; bool external = 3; bool abr = 4; bool router = 5; bool v6 = 6; } message LsIGPFlags { bool down = 1; bool no_unicast = 2; bool local_address = 3; bool propagate_nssa = 4; } message LsSrRange { uint32 begin = 1; uint32 end = 2; } message LsSrCapabilities { bool ipv4_supported = 1; bool ipv6_supported = 2; repeated LsSrRange ranges = 3; } message LsSrLocalBlock { repeated LsSrRange ranges = 1; } message LsAttributeNode { string name = 1; LsNodeFlags flags = 2; string local_router_id = 3; string local_router_id_v6 = 4; bytes isis_area = 5; bytes opaque = 6; LsSrCapabilities sr_capabilities = 7; bytes sr_algorithms = 8; LsSrLocalBlock sr_local_block = 9; } message LsAttributeLink { string name = 1; string local_router_id = 2; string local_router_id_v6 = 3; string remote_router_id = 4; string remote_router_id_v6 = 5; uint32 admin_group = 6; uint32 default_te_metric = 7; uint32 igp_metric = 8; bytes opaque = 9; float bandwidth = 10; float reservable_bandwidth = 11; repeated float unreserved_bandwidth = 12; uint32 sr_adjacency_sid = 13; repeated uint32 srlgs = 14; } message LsAttributePrefix { LsIGPFlags igp_flags = 1; bytes opaque = 2; uint32 sr_prefix_sid = 3; } message LsBgpPeerSegmentSIDFlags { bool value = 1; bool local = 2; bool backup = 3; bool persistent = 4; } message LsBgpPeerSegmentSID { LsBgpPeerSegmentSIDFlags flags = 1; uint32 weight = 2; uint32 sid = 3; } message LsAttributeBgpPeerSegment { LsBgpPeerSegmentSID bgp_peer_node_sid = 1; LsBgpPeerSegmentSID bgp_peer_adjacency_sid = 2; LsBgpPeerSegmentSID bgp_peer_set_sid = 3; } message LsAttribute { LsAttributeNode node = 1; LsAttributeLink link = 2; LsAttributePrefix prefix = 3; LsAttributeBgpPeerSegment bgp_peer_segment = 4; } message UnknownAttribute { uint32 flags = 1; uint32 type = 2; bytes value = 3; } // https://www.rfc-editor.org/rfc/rfc9252.html#section-3.2.1 message SRv6StructureSubSubTLV { uint32 locator_block_length = 1; uint32 locator_node_length = 2; uint32 function_length = 3; uint32 argument_length = 4; uint32 transposition_length = 5; uint32 transposition_offset = 6; } message SRv6SIDFlags { // Placeholder for future sid flags bool flag_1 = 1; } message SRv6TLV { repeated google.protobuf.Any tlv = 1; } // https://tools.ietf.org/html/draft-dawra-bess-srv6-services-02#section-2.1.1 message SRv6InformationSubTLV { bytes sid = 1; SRv6SIDFlags flags = 2; uint32 endpoint_behavior = 3; // SRv6TLV is one of: // - SRv6StructureSubSubTLV map sub_sub_tlvs = 4; } // https://www.rfc-editor.org/rfc/rfc9252.html#section-2 message SRv6L3ServiceTLV { // SRv6TLV is one of: // - SRv6InformationSubTLV map sub_tlvs = 1; } // https://www.rfc-editor.org/rfc/rfc9252.html#section-2 message SRv6L2ServiceTLV { // SRv6TLV is one of: // - SRv6InformationSubTLV map sub_tlvs = 1; } // https://tools.ietf.org/html/rfc8669 message PrefixSID { // tlv is one of: // - IndexLabelTLV Type 1 (not yet implemented) // - OriginatorSRGBTLV Type 3 (not yet implemented) // - SRv6L3ServiceTLV Type 5 // - SRv6L2ServiceTLV Type 6 repeated google.protobuf.Any tlvs = 1; } pavel-odintsov-fastnetmon-394fbe0/src/gobgp_client/gobgp.proto000066400000000000000000000671001520703010000246540ustar00rootroot00000000000000// Copyright (C) 2015-2017 Nippon Telegraph and Telephone Corporation. // // 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. syntax = "proto3"; package apipb; option go_package = "github.com/osrg/gobgp/v3/api;apipb"; import "google/protobuf/any.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; // Interface exported by the server. service GobgpApi { rpc StartBgp(StartBgpRequest) returns(google.protobuf.Empty); rpc StopBgp(StopBgpRequest) returns(google.protobuf.Empty); rpc GetBgp(GetBgpRequest) returns(GetBgpResponse); rpc WatchEvent(WatchEventRequest) returns(stream WatchEventResponse); rpc AddPeer(AddPeerRequest) returns(google.protobuf.Empty); rpc DeletePeer(DeletePeerRequest) returns(google.protobuf.Empty); rpc ListPeer(ListPeerRequest) returns(stream ListPeerResponse); rpc UpdatePeer(UpdatePeerRequest) returns(UpdatePeerResponse); rpc ResetPeer(ResetPeerRequest) returns(google.protobuf.Empty); rpc ShutdownPeer(ShutdownPeerRequest) returns(google.protobuf.Empty); rpc EnablePeer(EnablePeerRequest) returns(google.protobuf.Empty); rpc DisablePeer(DisablePeerRequest) returns(google.protobuf.Empty); rpc AddPeerGroup(AddPeerGroupRequest) returns(google.protobuf.Empty); rpc DeletePeerGroup(DeletePeerGroupRequest) returns(google.protobuf.Empty); rpc ListPeerGroup(ListPeerGroupRequest) returns(stream ListPeerGroupResponse); rpc UpdatePeerGroup(UpdatePeerGroupRequest) returns(UpdatePeerGroupResponse); rpc AddDynamicNeighbor(AddDynamicNeighborRequest) returns(google.protobuf.Empty); rpc ListDynamicNeighbor(ListDynamicNeighborRequest) returns(stream ListDynamicNeighborResponse); rpc DeleteDynamicNeighbor(DeleteDynamicNeighborRequest) returns(google.protobuf.Empty); rpc AddPath(AddPathRequest) returns(AddPathResponse); rpc DeletePath(DeletePathRequest) returns(google.protobuf.Empty); rpc ListPath(ListPathRequest) returns(stream ListPathResponse); rpc AddPathStream(stream AddPathStreamRequest) returns(google.protobuf.Empty); rpc GetTable(GetTableRequest) returns(GetTableResponse); rpc AddVrf(AddVrfRequest) returns(google.protobuf.Empty); rpc DeleteVrf(DeleteVrfRequest) returns(google.protobuf.Empty); rpc ListVrf(ListVrfRequest) returns(stream ListVrfResponse); rpc AddPolicy(AddPolicyRequest) returns(google.protobuf.Empty); rpc DeletePolicy(DeletePolicyRequest) returns(google.protobuf.Empty); rpc ListPolicy(ListPolicyRequest) returns(stream ListPolicyResponse); rpc SetPolicies(SetPoliciesRequest) returns(google.protobuf.Empty); rpc AddDefinedSet(AddDefinedSetRequest) returns(google.protobuf.Empty); rpc DeleteDefinedSet(DeleteDefinedSetRequest) returns(google.protobuf.Empty); rpc ListDefinedSet(ListDefinedSetRequest) returns(stream ListDefinedSetResponse); rpc AddStatement(AddStatementRequest) returns(google.protobuf.Empty); rpc DeleteStatement(DeleteStatementRequest) returns(google.protobuf.Empty); rpc ListStatement(ListStatementRequest) returns(stream ListStatementResponse); rpc AddPolicyAssignment(AddPolicyAssignmentRequest) returns(google.protobuf.Empty); rpc DeletePolicyAssignment(DeletePolicyAssignmentRequest) returns(google.protobuf.Empty); rpc ListPolicyAssignment(ListPolicyAssignmentRequest) returns(stream ListPolicyAssignmentResponse); rpc SetPolicyAssignment(SetPolicyAssignmentRequest) returns(google.protobuf.Empty); rpc AddRpki(AddRpkiRequest) returns(google.protobuf.Empty); rpc DeleteRpki(DeleteRpkiRequest) returns(google.protobuf.Empty); rpc ListRpki(ListRpkiRequest) returns(stream ListRpkiResponse); rpc EnableRpki(EnableRpkiRequest) returns(google.protobuf.Empty); rpc DisableRpki(DisableRpkiRequest) returns(google.protobuf.Empty); rpc ResetRpki(ResetRpkiRequest) returns(google.protobuf.Empty); rpc ListRpkiTable(ListRpkiTableRequest) returns(stream ListRpkiTableResponse); rpc EnableZebra(EnableZebraRequest) returns(google.protobuf.Empty); rpc EnableMrt(EnableMrtRequest) returns(google.protobuf.Empty); rpc DisableMrt(DisableMrtRequest) returns(google.protobuf.Empty); rpc AddBmp(AddBmpRequest) returns(google.protobuf.Empty); rpc DeleteBmp(DeleteBmpRequest) returns(google.protobuf.Empty); rpc ListBmp(ListBmpRequest) returns(stream ListBmpResponse); rpc SetLogLevel(SetLogLevelRequest) returns(google.protobuf.Empty); } message StartBgpRequest { Global global = 1; } message StopBgpRequest {} message GetBgpRequest {} message GetBgpResponse { Global global = 1; } message WatchEventRequest { message Peer { } Peer peer = 1; message Table { message Filter { enum Type { BEST = 0; ADJIN = 1; POST_POLICY = 2; } Type type = 1; bool init = 2; } repeated Filter filters = 1; } Table table = 2; } message WatchEventResponse { message PeerEvent { enum Type { UNKNOWN = 0; INIT = 1; END_OF_INIT = 2; STATE = 3; } Type type = 1; Peer peer = 2; } message TableEvent { repeated Path paths = 2; } oneof event { PeerEvent peer = 2; TableEvent table = 3; } } message AddPeerRequest { Peer peer = 1; } message DeletePeerRequest { string address = 1; string interface = 2; } message ListPeerRequest { string address = 1; bool enableAdvertised = 2; } message ListPeerResponse { Peer peer = 1; } message UpdatePeerRequest { Peer peer = 1; // Calls SoftResetIn after updating the peer configuration if needed. bool do_soft_reset_in = 2; } message UpdatePeerResponse { // Indicates whether calling SoftResetIn is required due to this update. If // "true" is set, the client should call SoftResetIn manually. If // "do_soft_reset_in = true" is set in the request, always returned with // "false". bool needs_soft_reset_in = 1; } message ResetPeerRequest { string address = 1; string communication = 2; bool soft = 3; enum SoftResetDirection { IN = 0; OUT = 1; BOTH = 2; } SoftResetDirection direction = 4; } message ShutdownPeerRequest { string address = 1; string communication = 2; } message EnablePeerRequest { string address = 1; } message DisablePeerRequest { string address = 1; string communication = 2; } message AddPeerGroupRequest { PeerGroup peer_group = 1; } message DeletePeerGroupRequest { string name = 1; } message UpdatePeerGroupRequest { PeerGroup peer_group = 1; bool do_soft_reset_in = 2; } message UpdatePeerGroupResponse { bool needs_soft_reset_in = 1; } message ListPeerGroupRequest { string peer_group_name = 1; } message ListPeerGroupResponse { PeerGroup peer_group = 1; } message AddDynamicNeighborRequest { DynamicNeighbor dynamic_neighbor = 1; } message DeleteDynamicNeighborRequest { string prefix = 1; string peer_group = 2; } message ListDynamicNeighborRequest { string peer_group = 1; } message ListDynamicNeighborResponse { DynamicNeighbor dynamic_neighbor = 1; } message AddPathRequest { TableType table_type = 1; string vrf_id = 2; Path path = 3; } message AddPathResponse { bytes uuid = 1; } message DeletePathRequest { TableType table_type = 1; string vrf_id = 2; Family family = 3; Path path = 4; bytes uuid = 5; } // API representation of table.LookupPrefix message TableLookupPrefix { // API representation of table.LookupOption enum Type { EXACT = 0; LONGER = 1; SHORTER = 2; } string prefix = 1; Type type = 2; } message ListPathRequest { TableType table_type = 1; string name = 2; Family family = 3; repeated TableLookupPrefix prefixes = 4; enum SortType { NONE = 0; PREFIX = 1; } SortType sort_type = 5; bool enable_filtered = 6; bool enable_nlri_binary = 7; bool enable_attribute_binary = 8; // enable_only_binary == true means that only nlri_binary and pattrs_binary // will be used instead of nlri and pattrs for each Path in ListPathResponse. bool enable_only_binary = 9; } message ListPathResponse { Destination destination = 1; } message AddPathStreamRequest { TableType table_type = 1; string vrf_id = 2; repeated Path paths = 3; } message GetTableRequest { TableType table_type = 1; Family family = 2; string name = 3; } message GetTableResponse { uint64 num_destination = 1; uint64 num_path = 2; uint64 num_accepted = 3; // only meaningful when type == ADJ_IN } message AddVrfRequest { Vrf vrf = 1; } message DeleteVrfRequest { string name = 1; } message ListVrfRequest { string name = 1; } message ListVrfResponse { Vrf vrf = 1; } message AddPolicyRequest { Policy policy = 1; // if this flag is set, gobgpd won't define new statements // but refer existing statements using statement's names in this arguments. bool refer_existing_statements = 2; } message DeletePolicyRequest { Policy policy = 1; // if this flag is set, gobgpd won't delete any statements // even if some statements get not used by any policy by this operation. bool preserve_statements = 2; bool all = 3; } message ListPolicyRequest { string name = 1; } message ListPolicyResponse { Policy policy = 1; } message SetPoliciesRequest { repeated DefinedSet defined_sets = 1; repeated Policy policies = 2; repeated PolicyAssignment assignments = 3; } message AddDefinedSetRequest { DefinedSet defined_set = 1; } message DeleteDefinedSetRequest { DefinedSet defined_set = 1; bool all = 2; } message ListDefinedSetRequest { DefinedType defined_type = 1; string name = 2; } message ListDefinedSetResponse { DefinedSet defined_set = 1; } message AddStatementRequest { Statement statement = 1; } message DeleteStatementRequest { Statement statement = 1; bool all = 2; } message ListStatementRequest { string name = 1; } message ListStatementResponse { Statement statement = 1; } message AddPolicyAssignmentRequest { PolicyAssignment assignment = 1; } message DeletePolicyAssignmentRequest { PolicyAssignment assignment = 1; bool all = 2; } message ListPolicyAssignmentRequest { string name = 1; PolicyDirection direction = 2; } message ListPolicyAssignmentResponse { PolicyAssignment assignment = 1; } message SetPolicyAssignmentRequest { PolicyAssignment assignment = 1; } message AddRpkiRequest { string address = 1; uint32 port = 2; int64 lifetime = 3; } message DeleteRpkiRequest { string address = 1; uint32 port = 2; } message ListRpkiRequest { Family family = 1; } message ListRpkiResponse { Rpki server = 1; } message EnableRpkiRequest { string address = 1; uint32 port = 2; } message DisableRpkiRequest { string address = 1; uint32 port = 2; } message ResetRpkiRequest { string address = 1; uint32 port = 2; bool soft = 3; } message ListRpkiTableRequest { Family family = 1; } message ListRpkiTableResponse { Roa roa = 1; } message EnableZebraRequest { string url = 1; repeated string route_types = 2; uint32 version = 3; bool nexthop_trigger_enable = 4; uint32 nexthop_trigger_delay = 5; uint32 mpls_label_range_size = 6; string software_name = 7; } message EnableMrtRequest { enum DumpType { UPDATES = 0; TABLE = 1; } DumpType type = 1; string filename = 2; uint64 dump_interval = 3; uint64 rotation_interval = 4; } message DisableMrtRequest { string filename = 1; } message AddBmpRequest { string address = 1; uint32 port = 2; enum MonitoringPolicy { PRE = 0; POST = 1; BOTH = 2; LOCAL = 3; ALL = 4; } MonitoringPolicy policy = 3; int32 StatisticsTimeout = 4; string SysName = 5; string SysDescr = 6; } message DeleteBmpRequest { string address = 1; uint32 port = 2; } message ListBmpRequest {} message ListBmpResponse { message BmpStation { message Conf { string address = 1; uint32 port = 2; } Conf conf = 1; message State { google.protobuf.Timestamp uptime = 1; google.protobuf.Timestamp downtime = 2; } State state = 2; } BmpStation station = 1; } message Family { enum Afi { AFI_UNKNOWN = 0; AFI_IP = 1; AFI_IP6 = 2; AFI_L2VPN = 25; AFI_LS = 16388; AFI_OPAQUE = 16397; } enum Safi { SAFI_UNKNOWN = 0; SAFI_UNICAST = 1; SAFI_MULTICAST = 2; SAFI_MPLS_LABEL = 4; SAFI_ENCAPSULATION = 7; SAFI_VPLS = 65; SAFI_EVPN = 70; SAFI_LS = 71; SAFI_SR_POLICY = 73; SAFI_MUP = 85; SAFI_MPLS_VPN = 128; SAFI_MPLS_VPN_MULTICAST = 129; SAFI_ROUTE_TARGET_CONSTRAINTS = 132; SAFI_FLOW_SPEC_UNICAST = 133; SAFI_FLOW_SPEC_VPN = 134; SAFI_KEY_VALUE = 241; } Afi afi = 1; Safi safi = 2; } enum TableType { GLOBAL = 0; LOCAL = 1; ADJ_IN = 2; ADJ_OUT = 3; VRF = 4; } message Validation { enum State { STATE_NONE = 0; STATE_NOT_FOUND = 1; STATE_VALID = 2; STATE_INVALID = 3; } enum Reason { REASON_NONE = 0; REASON_ASN = 1; REASON_LENGTH = 2; } State state = 1; Reason reason = 2; repeated Roa matched = 3; repeated Roa unmatched_asn = 4; repeated Roa unmatched_length = 5; } message Path { // One of the following defined in "api/attribute.proto": // - IPAddressPrefix // - LabeledIPAddressPrefix // - EncapsulationNLRI // - EVPNEthernetAutoDiscoveryRoute // - EVPNMACIPAdvertisementRoute // - EVPNInclusiveMulticastEthernetTagRoute // - EVPNEthernetSegmentRoute // - EVPNIPPrefixRoute // - EVPNIPMSIRoute // - LabeledVPNIPAddressPrefix // - RouteTargetMembershipNLRI // - FlowSpecNLRI // - VPNFlowSpecNLRI // - OpaqueNLRI // - LsAddrPrefix // - SRPolicyNLRI // - MUPInterworkSegmentDiscoveryRoute // - MUPDirectSegmentDiscoveryRoute // - MUPType1SessionTransformedRoute // - MUPType2SessionTransformedRoute google.protobuf.Any nlri = 1; // Each attribute must be one of *Attribute defined in // "api/attribute.proto". repeated google.protobuf.Any pattrs = 2; google.protobuf.Timestamp age = 3; bool best = 4; bool is_withdraw = 5; Validation validation = 7; bool no_implicit_withdraw = 8; Family family = 9; uint32 source_asn = 10; string source_id = 11; bool filtered = 12; bool stale = 13; bool is_from_external = 14; string neighbor_ip = 15; bytes uuid = 16; // only paths installed by AddPath API have this bool is_nexthop_invalid = 17; uint32 identifier = 18; uint32 local_identifier = 19; bytes nlri_binary = 20; repeated bytes pattrs_binary = 21; } message Destination { string prefix = 1; repeated Path paths = 2; } message Peer { ApplyPolicy apply_policy = 1; PeerConf conf = 2; EbgpMultihop ebgp_multihop = 3; RouteReflector route_reflector = 4; PeerState state = 5; Timers timers = 6; Transport transport = 7; RouteServer route_server = 8; GracefulRestart graceful_restart = 9; repeated AfiSafi afi_safis = 10; TtlSecurity ttl_security = 11; } message PeerGroup { ApplyPolicy apply_policy = 1; PeerGroupConf conf = 2; EbgpMultihop ebgp_multihop = 3; RouteReflector route_reflector = 4; PeerGroupState info = 5; Timers timers = 6; Transport transport = 7; RouteServer route_server = 8; GracefulRestart graceful_restart = 9; repeated AfiSafi afi_safis = 10; TtlSecurity ttl_security = 11; } message DynamicNeighbor { string prefix = 1; string peer_group = 2; } message ApplyPolicy { PolicyAssignment in_policy = 1; PolicyAssignment export_policy = 2; PolicyAssignment import_policy = 3; } message PrefixLimit { Family family = 1; uint32 max_prefixes = 2; uint32 shutdown_threshold_pct = 3; } enum PeerType { INTERNAL = 0; EXTERNAL = 1; } enum RemovePrivate { REMOVE_NONE = 0; REMOVE_ALL = 1; REPLACE = 2; } message PeerConf { string auth_password = 1; string description = 2; uint32 local_asn = 3; string neighbor_address = 4; uint32 peer_asn = 5; string peer_group = 6; PeerType type = 7; RemovePrivate remove_private = 8; bool route_flap_damping = 9; uint32 send_community = 10; string neighbor_interface = 11; string vrf = 12; uint32 allow_own_asn = 13; bool replace_peer_asn = 14; bool admin_down = 15; bool send_software_version = 16; } message PeerGroupConf { string auth_password = 1; string description = 2; uint32 local_asn = 3; uint32 peer_asn = 4; string peer_group_name = 5; PeerType type = 6; RemovePrivate remove_private = 7; bool route_flap_damping = 8; uint32 send_community = 9; } message PeerGroupState { string auth_password = 1; string description = 2; uint32 local_asn = 3; uint32 peer_asn = 4; string peer_group_name = 5; PeerType type = 6; RemovePrivate remove_private = 7; bool route_flap_damping = 8; uint32 send_community = 9; uint32 total_paths = 10; uint32 total_prefixes = 11; } message TtlSecurity { bool enabled = 1; uint32 ttl_min = 2; } message EbgpMultihop { bool enabled = 1; uint32 multihop_ttl = 2; } message RouteReflector { bool route_reflector_client = 1; string route_reflector_cluster_id = 2; } message PeerState { string auth_password = 1; string description = 2; uint32 local_asn = 3; Messages messages = 4; string neighbor_address = 5; uint32 peer_asn = 6; string peer_group = 7; PeerType type = 8; Queues queues = 9; RemovePrivate remove_private = 10; bool route_flap_damping = 11; uint32 send_community = 12; enum SessionState { UNKNOWN = 0; IDLE = 1; CONNECT = 2; ACTIVE = 3; OPENSENT = 4; OPENCONFIRM = 5; ESTABLISHED = 6; } SessionState session_state = 13; enum AdminState { UP = 0; DOWN = 1; PFX_CT = 2; // prefix counter over limit } AdminState admin_state = 15; uint32 out_q = 16; uint32 flops = 17; // Each attribute must be one of *Capability defined in // "api/capability.proto". repeated google.protobuf.Any remote_cap = 18; repeated google.protobuf.Any local_cap = 19; string router_id = 20; } message Messages { Message received = 1; Message sent = 2; } message Message { uint64 notification = 1; uint64 update = 2; uint64 open = 3; uint64 keepalive = 4; uint64 refresh = 5; uint64 discarded = 6; uint64 total = 7; uint64 withdraw_update = 8; uint64 withdraw_prefix = 9; } message Queues { uint32 input = 1; uint32 output = 2; } message Timers { TimersConfig config = 1; TimersState state = 2; } message TimersConfig { uint64 connect_retry = 1; uint64 hold_time = 2; uint64 keepalive_interval = 3; uint64 minimum_advertisement_interval = 4; uint64 idle_hold_time_after_reset = 5; } message TimersState { uint64 connect_retry = 1; uint64 hold_time = 2; uint64 keepalive_interval = 3; uint64 minimum_advertisement_interval = 4; uint64 negotiated_hold_time = 5; google.protobuf.Timestamp uptime = 6; google.protobuf.Timestamp downtime = 7; } message Transport { string local_address = 1; uint32 local_port = 2; bool mtu_discovery = 3; bool passive_mode = 4; string remote_address = 5; uint32 remote_port = 6; uint32 tcp_mss = 7; string bind_interface = 8; } message RouteServer { bool route_server_client = 1; bool secondary_route = 2; } message GracefulRestart { bool enabled = 1; uint32 restart_time = 2; bool helper_only = 3; uint32 deferral_time = 4; bool notification_enabled = 5; bool longlived_enabled = 6; uint32 stale_routes_time = 7; uint32 peer_restart_time = 8; bool peer_restarting = 9; bool local_restarting = 10; string mode = 11; } message MpGracefulRestartConfig { bool enabled = 1; } message MpGracefulRestartState { bool enabled = 1; bool received = 2; bool advertised = 3; bool end_of_rib_received = 4; bool end_of_rib_sent = 5; } message MpGracefulRestart { MpGracefulRestartConfig config = 1; MpGracefulRestartState state = 2; } message AfiSafiConfig { Family family = 1; bool enabled = 2; } message AfiSafiState { Family family = 1; bool enabled = 2; uint64 received = 3; uint64 accepted = 4; uint64 advertised = 5; } message RouteSelectionOptionsConfig { bool always_compare_med = 1; bool ignore_as_path_length = 2; bool external_compare_router_id = 3; bool advertise_inactive_routes = 4; bool enable_aigp = 5; bool ignore_next_hop_igp_metric = 6; bool disable_best_path_selection = 7; } message RouteSelectionOptionsState { bool always_compare_med = 1; bool ignore_as_path_length = 2; bool external_compare_router_id = 3; bool advertise_inactive_routes = 4; bool enable_aigp = 5; bool ignore_next_hop_igp_metric = 6; bool disable_best_path_selection = 7; } message RouteSelectionOptions { RouteSelectionOptionsConfig config = 1; RouteSelectionOptionsState state = 2; } message UseMultiplePathsConfig { bool enabled = 1; } message UseMultiplePathsState { bool enabled = 1; } message EbgpConfig { bool allow_multiple_asn = 1; uint32 maximum_paths = 2; } message EbgpState { bool allow_multiple_asn = 1; uint32 maximum_paths = 2; } message Ebgp { EbgpConfig config = 1; EbgpState state = 2; } message IbgpConfig { uint32 maximum_paths = 1; } message IbgpState { uint32 maximum_paths = 1; } message Ibgp { IbgpConfig config = 1; IbgpState state = 2; } message UseMultiplePaths { UseMultiplePathsConfig config = 1; UseMultiplePathsState state = 2; Ebgp ebgp = 3; Ibgp ibgp = 4; } message RouteTargetMembershipConfig { uint32 deferral_time = 1; } message RouteTargetMembershipState { uint32 deferral_time = 1; } message RouteTargetMembership { RouteTargetMembershipConfig config = 1; RouteTargetMembershipState state = 2; } message LongLivedGracefulRestartConfig { bool enabled = 1; uint32 restart_time = 2; } message LongLivedGracefulRestartState { bool enabled = 1; bool received = 2; bool advertised = 3; uint32 peer_restart_time = 4; bool peer_restart_timer_expired = 5; } message LongLivedGracefulRestart { LongLivedGracefulRestartConfig config = 1; LongLivedGracefulRestartState state = 2; } message AfiSafi { MpGracefulRestart mp_graceful_restart = 1; AfiSafiConfig config = 2; AfiSafiState state = 3; ApplyPolicy apply_policy = 4; // TODO: // Support the following structures: // - Ipv4Unicast // - Ipv6Unicast // - Ipv4LabelledUnicast // - Ipv6LabelledUnicast // - L3vpnIpv4Unicast // - L3vpnIpv6Unicast // - L3vpnIpv4Multicast // - L3vpnIpv6Multicast // - L2vpnVpls // - L2vpnEvpn RouteSelectionOptions route_selection_options = 5; UseMultiplePaths use_multiple_paths = 6; PrefixLimit prefix_limits = 7; RouteTargetMembership route_target_membership = 8; LongLivedGracefulRestart long_lived_graceful_restart = 9; AddPaths add_paths = 10; } message AddPathsConfig { bool receive = 1; uint32 send_max = 2; } message AddPathsState { bool receive = 1; uint32 send_max = 2; } message AddPaths { AddPathsConfig config = 1; AddPathsState state = 2; } message Prefix { string ip_prefix = 1; uint32 mask_length_min = 2; uint32 mask_length_max = 3; } enum DefinedType { PREFIX = 0; NEIGHBOR = 1; TAG = 2; AS_PATH = 3; COMMUNITY = 4; EXT_COMMUNITY = 5; LARGE_COMMUNITY = 6; NEXT_HOP = 7; } message DefinedSet { DefinedType defined_type = 1; string name = 2; repeated string list = 3; repeated Prefix prefixes = 4; } message MatchSet { enum Type { ANY = 0; ALL = 1; INVERT = 2; } Type type = 1; string name = 2; } message AsPathLength { enum Type { EQ = 0; GE = 1; LE = 2; } Type type = 1; uint32 length = 2; } message Conditions { MatchSet prefix_set = 1; MatchSet neighbor_set = 2; AsPathLength as_path_length = 3; MatchSet as_path_set = 4; MatchSet community_set = 5; MatchSet ext_community_set = 6; int32 rpki_result = 7; enum RouteType { ROUTE_TYPE_NONE = 0; ROUTE_TYPE_INTERNAL = 1; ROUTE_TYPE_EXTERNAL = 2; ROUTE_TYPE_LOCAL = 3; } RouteType route_type = 8; MatchSet large_community_set = 9; repeated string next_hop_in_list = 10; repeated Family afi_safi_in = 11; } enum RouteAction { NONE = 0; ACCEPT = 1; REJECT = 2; } message CommunityAction { enum Type { ADD = 0; REMOVE = 1; REPLACE = 2; } Type type = 1; repeated string communities = 2; } message MedAction { enum Type { MOD = 0; REPLACE = 1; } Type type = 1; int64 value = 2; } message AsPrependAction { uint32 asn = 1; uint32 repeat = 2; bool use_left_most = 3; } message NexthopAction { string address = 1; bool self = 2; bool unchanged = 3; } message LocalPrefAction { uint32 value = 1; } message Actions { RouteAction route_action = 1; CommunityAction community = 2; MedAction med = 3; AsPrependAction as_prepend = 4; CommunityAction ext_community = 5; NexthopAction nexthop = 6; LocalPrefAction local_pref = 7; CommunityAction large_community = 8; } message Statement { string name = 1; Conditions conditions = 2; Actions actions = 3; } message Policy { string name = 1; repeated Statement statements = 2; } enum PolicyDirection { UNKNOWN = 0; IMPORT = 1; EXPORT = 2; } message PolicyAssignment { string name = 1; PolicyDirection direction = 2; repeated Policy policies = 4; RouteAction default_action = 5; } message RoutingPolicy { repeated DefinedSet defined_sets = 1; repeated Policy policies = 2; } message Roa { uint32 asn = 1; uint32 prefixlen = 2; uint32 maxlen = 3; string prefix = 4; RPKIConf conf = 5; } message Vrf { string name = 1; // Route Distinguisher must be one of // RouteDistinguisherTwoOctetAS, // RouteDistinguisherIPAddressAS, // or RouteDistinguisherFourOctetAS. google.protobuf.Any rd = 2; // List of the Import Route Targets. Each must be one of // TwoOctetAsSpecificExtended, // IPv4AddressSpecificExtended, // or FourOctetAsSpecificExtended. repeated google.protobuf.Any import_rt = 3; // List of the Export Route Targets. Each must be one of // TwoOctetAsSpecificExtended, // IPv4AddressSpecificExtended, // or FourOctetAsSpecificExtended. repeated google.protobuf.Any export_rt = 4; uint32 id = 5; } message DefaultRouteDistance { uint32 external_route_distance = 1; uint32 internal_route_distance = 2; } message Global { uint32 asn = 1; string router_id = 2; int32 listen_port = 3; repeated string listen_addresses = 4; repeated uint32 families = 5; bool use_multiple_paths = 6; RouteSelectionOptionsConfig route_selection_options = 7; DefaultRouteDistance default_route_distance = 8; Confederation confederation = 9; GracefulRestart graceful_restart = 10; ApplyPolicy apply_policy = 11; string bind_to_device = 12; } message Confederation { bool enabled = 1; uint32 identifier = 2; repeated uint32 member_as_list = 3; } message RPKIConf { string address = 1; uint32 remote_port = 2; } message RPKIState { google.protobuf.Timestamp uptime = 1; google.protobuf.Timestamp downtime = 2; bool up = 3; uint32 record_ipv4 = 4; uint32 record_ipv6 = 5; uint32 prefix_ipv4 = 6; uint32 prefix_ipv6 = 7; uint32 serial = 8; int64 received_ipv4 = 9; int64 received_ipv6 = 10; int64 serial_notify = 11; int64 cache_reset = 12; int64 cache_response = 13; int64 end_of_data = 14; int64 error = 15; int64 serial_query = 16; int64 reset_query = 17; } message Rpki { RPKIConf conf = 1; RPKIState state = 2; } message SetLogLevelRequest { enum Level { PANIC = 0; FATAL = 1; ERROR = 2; WARN = 3; INFO = 4; DEBUG = 5; TRACE = 6; } Level level = 1; } pavel-odintsov-fastnetmon-394fbe0/src/gobgp_client/gobgp_client.cpp000066400000000000000000000253241520703010000256330ustar00rootroot00000000000000#include "gobgp_client.hpp" #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif // __GNUC__ // // MinGW has quite weird definitions which clash with field names in gRPC bindinds // We need to apply some trickery to avoid complilation errors: // https://github.com/pavel-odintsov/fastnetmon/issues/977 // #ifdef _WIN32 // Save previous values of these defines #pragma push_macro("interface") #pragma push_macro("IN") #pragma push_macro("OUT") #undef interface #undef IN #undef OUT #endif #include "../gobgp_client/attribute.pb.h" #ifdef _WIN32 // Restore original values of these defines #pragma pop_macro("interface") #pragma pop_macro("IN") #pragma pop_macro("OUT") #endif #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // __GNUC__ #include "../all_logcpp_libraries.hpp" #include "../fast_library.hpp" unsigned int gobgp_client_connection_timeout = 5; extern log4cpp::Category& logger; GrpcClient::GrpcClient(std::shared_ptr channel) : stub_(apipb::GobgpApi::NewStub(channel)) { } // Announce unicast or flow spec bool GrpcClient::AnnounceCommonPrefix(dynamic_binary_buffer_t binary_nlri, std::vector bgp_attributes, bool is_withdrawal, unsigned int afi, unsigned int safi) { // We're not going to free this memory and we delegate it to gRPC // but we need to tell about it to PVS //+V773:SUPPRESS, class:Path, namespace:apipb apipb::Path* current_path = new apipb::Path; if (is_withdrawal) { current_path->set_is_withdraw(true); } // We're not going to free this memory and we delegate it to gRPC // but we need to tell about it to PVS //+V773:SUPPRESS, class:Family, namespace:apipb auto route_family = new apipb::Family; if (afi == AFI_IP) { route_family->set_afi(apipb::Family::AFI_IP); } else if (afi == AFI_IP6) { route_family->set_afi(apipb::Family::AFI_IP6); } else { logger << log4cpp::Priority::ERROR << "Unknown AFI"; return false; } if (safi == SAFI_UNICAST) { route_family->set_safi(apipb::Family::SAFI_UNICAST); } else if (safi == SAFI_FLOW_SPEC_UNICAST) { route_family->set_safi(apipb::Family::SAFI_FLOW_SPEC_UNICAST); } else { logger << log4cpp::Priority::ERROR << "Unknown SAFI"; return false; } current_path->set_allocated_family(route_family); current_path->set_nlri_binary(binary_nlri.get_pointer(), binary_nlri.get_used_size()); for (auto bgp_attribute : bgp_attributes) { current_path->add_pattrs_binary(bgp_attribute.get_pointer(), bgp_attribute.get_used_size()); } apipb::AddPathRequest request; request.set_table_type(apipb::TableType::GLOBAL); request.set_allocated_path(current_path); grpc::ClientContext context; // Set timeout for API std::chrono::system_clock::time_point deadline = std::chrono::system_clock::now() + std::chrono::seconds(gobgp_client_connection_timeout); context.set_deadline(deadline); apipb::AddPathResponse response; // Don't be confused by name, it also can withdraw announces auto status = stub_->AddPath(&context, request, &response); if (!status.ok()) { logger << log4cpp::Priority::ERROR << "AddPath request to BGP daemon failed with code: " << status.error_code() << " message " << status.error_message(); return false; } return true; } bool GrpcClient::AnnounceUnicastPrefixLowLevelIPv4(const IPv4UnicastAnnounce& unicast_ipv4_announce, bool is_withdrawal) { logger << log4cpp::Priority::INFO << "Send IPv4 " << (is_withdrawal ? "withdrawal " : "") << "unicast announce to BGP daemon: " << unicast_ipv4_announce.print(); dynamic_binary_buffer_t binary_nlri; auto binary_nlri_generation_result = unicast_ipv4_announce.generate_nlri(binary_nlri); if (!binary_nlri_generation_result) { logger << log4cpp::Priority::ERROR << "Could not encode NLRI for IPv4 unicast announce due to unsuccessful error code"; return false; } if (binary_nlri.get_used_size() == 0 or binary_nlri.get_pointer() == NULL) { logger << log4cpp::Priority::ERROR << "Could not encode NLRI for IPv4 unicast announce"; return false; } std::vector bgp_attributes; if (!unicast_ipv4_announce.get_attributes(bgp_attributes)) { logger << log4cpp::Priority::ERROR << "Could not get attributes for IPv4 unicast announce"; return false; } if (bgp_attributes.size() == 0) { logger << log4cpp::Priority::ERROR << "We got zero number of attributes"; return false; } logger << log4cpp::Priority::DEBUG << "Got " << bgp_attributes.size() << " BGP attributes"; return AnnounceCommonPrefix(binary_nlri, bgp_attributes, is_withdrawal, AFI_IP, SAFI_UNICAST); } bool GrpcClient::AnnounceUnicastPrefixLowLevelIPv6(const IPv6UnicastAnnounce& unicast_ipv6_announce, bool is_withdrawal) { logger << log4cpp::Priority::INFO << "Send IPv6 " << (is_withdrawal ? "withdrawal " : "") << "unicast announce to BGP daemon: " << unicast_ipv6_announce.print(); // We need to prepare very fancy NLRI first: https://github.com/osrg/gobgp/issues/2673 // To be more specific: // https://github.com/fujita/gobgp/blob/7e4d9a0e89b1fc5e4fc9865b7b6431a00dcb60e2/pkg/server/grpc_server_test.go#L48 // And implementation details: // https://github.com/osrg/gobgp/blob/master/pkg/packet/bgp/bgp.go#L1501 // https://github.com/osrg/gobgp/blob/master/pkg/packet/bgp/bgp.go#L1440 dynamic_binary_buffer_t ipv6_nlri{}; ipv6_nlri.set_buffer_size_in_bytes(256); if (!encode_ipv6_prefix(unicast_ipv6_announce.get_prefix(), ipv6_nlri)) { logger << log4cpp::Priority::ERROR << "Cannot encode prefix for IPv6 NLRI"; return false; } // Normally, vector should be ordered in ascending order of attribute types // with the only exception for bgp_mp_reach_ipv6_attribute std::vector bgp_attributes; dynamic_binary_buffer_t bgp_mp_reach_ipv6_attribute; bool craft_ipv6_mpreach_nlri_result = encode_ipv6_announces_into_bgp_mp_reach_attribute(unicast_ipv6_announce, bgp_mp_reach_ipv6_attribute); if (!craft_ipv6_mpreach_nlri_result) { logger << log4cpp::Priority::ERROR << "Can't encode MP reach NLRI for IPv6 announce"; return false; } logger << log4cpp::Priority::DEBUG << "IPv6 MP reach NLRI attribute size is: " << bgp_mp_reach_ipv6_attribute.get_used_size(); bgp_attributes.push_back(bgp_mp_reach_ipv6_attribute); bgp_attribute_origin origin_attr; dynamic_binary_buffer_t origin_as_binary_array; origin_as_binary_array.set_buffer_size_in_bytes(sizeof(origin_attr)); origin_as_binary_array.append_data_as_object_ptr(&origin_attr); // It has attribute #1 and will be first in all the cases bgp_attributes.push_back(origin_as_binary_array); // TODO: this logic is copied as is from get_attributes() of IPv4 announces // We need to try ways to unify logic to craft such announces // AS Path should be here and it's #2 if (unicast_ipv6_announce.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) + unicast_ipv6_announce.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_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 = unicast_ipv6_announce.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 number: " << unicast_ipv6_announce.as_path_asns.size(); for (auto asn : unicast_ipv6_announce.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"; } bgp_attributes.push_back(as_path_as_binary_array); } auto community_list = unicast_ipv6_announce.get_communities(); if (!community_list.empty()) { // TODO: I copied this code from bgp_protocol.cpp from get_attributes() function. I think we can unify it // 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_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 bgp_attributes.push_back(communities_list_as_binary_array); } // Normally NLRI is empty for IPv6 announces but GoBGP uses pretty unusual approach to encode it described on top of this function return AnnounceCommonPrefix(ipv6_nlri, bgp_attributes, is_withdrawal, AFI_IP6, SAFI_UNICAST); } pavel-odintsov-fastnetmon-394fbe0/src/gobgp_client/gobgp_client.hpp000066400000000000000000000027531520703010000256410ustar00rootroot00000000000000#pragma once #include #include #include "../bgp_protocol.hpp" #include "../fastnetmon_networks.hpp" // // MinGW has quite weird definitions which clash with field names in gRPC bindinds // We need to apply some trickery to avoid complilation errors: // https://github.com/pavel-odintsov/fastnetmon/issues/977 // #ifdef _WIN32 // Save previous values of these defines #pragma push_macro("interface") #pragma push_macro("IN") #pragma push_macro("OUT") #undef interface #undef IN #undef OUT #endif #include "../gobgp_client/gobgp.grpc.pb.h" #ifdef _WIN32 // Restore original values of these defines #pragma pop_macro("interface") #pragma pop_macro("IN") #pragma pop_macro("OUT") #endif #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // __GNUC__ class GrpcClient { public: GrpcClient(std::shared_ptr channel); // Announce unicast or flow spec bool AnnounceCommonPrefix(dynamic_binary_buffer_t binary_nlri, std::vector bgp_attributes, bool is_withdrawal, unsigned int afi, unsigned int safi); bool AnnounceUnicastPrefixLowLevelIPv4(const IPv4UnicastAnnounce& unicast_ipv4_announce, bool is_withdrawal); bool AnnounceUnicastPrefixLowLevelIPv6(const IPv6UnicastAnnounce& unicast_ipv6_announce, bool is_withdrawal); private: std::unique_ptr stub_; }; pavel-odintsov-fastnetmon-394fbe0/src/grep.sh000077500000000000000000000002741520703010000213300ustar00rootroot00000000000000#!/bin/bash script_dir=`dirname "$0"` find $script_dir/.. -type f | egrep -v 'fastnetmon.pb.cc|.git|build'> /tmp/file_list for i in `cat /tmp/file_list` ; do grep -Hi "$1" $i done pavel-odintsov-fastnetmon-394fbe0/src/iana/000077500000000000000000000000001520703010000207415ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/iana/iana_ethertypes.hpp000066400000000000000000000010271520703010000246360ustar00rootroot00000000000000#pragma once enum IanaEthertype : unsigned int { IanaEthertypeIPv4 = 2048, IanaEthertypeARP = 2054, // This one is not IANA certified, it's draft: https://datatracker.ietf.org/doc/html/draft-foschiano-erspan-03#section-4.2 IanaEthertypeERSPAN = 0x88BE, IanaEthertypeVLAN = 33024, IanaEthertypeIPv6 = 34525, IanaEthertypeMPLS_unicast = 34887, IanaEthertypeMPLS_multicast = 34888, IanaEthertypePPPoE_discovery = 34915, IanaEthertypePPPoE_session = 34916, }; pavel-odintsov-fastnetmon-394fbe0/src/iana/iana_ip_protocols.hpp000066400000000000000000000444651520703010000251730ustar00rootroot00000000000000#pragma once #include #include const char* get_ip_protocol_name_by_number_iana(uint8_t protocol_number); enum class ip_protocol_t : uint8_t { HOPOPT = 0, ICMP = 1, IGMP = 2, GGP = 3, IPV4 = 4, ST = 5, TCP = 6, CBT = 7, EGP = 8, IGP = 9, BBN_RCC_MON = 10, NVP_II = 11, PUP = 12, ARGUS_DEPRECATED = 13, EMCON = 14, XNET = 15, CHAOS = 16, UDP = 17, MUX = 18, DCN_MEAS = 19, HMP = 20, PRM = 21, XNS_IDP = 22, TRUNK_1 = 23, TRUNK_2 = 24, LEAF_1 = 25, LEAF_2 = 26, RDP = 27, IRTP = 28, ISO_TP4 = 29, NETBLT = 30, MFE_NSP = 31, MERIT_INP = 32, DCCP = 33, THREEPC = 34, IDPR = 35, XTP = 36, DDP = 37, IDPR_CMTP = 38, TPPPPP = 39, IL = 40, IPV6 = 41, SDRP = 42, IPV6_ROUTE = 43, IPV6_FRAG = 44, IDRP = 45, RSVP = 46, GRE = 47, DSR = 48, BNA = 49, ESP = 50, AH = 51, I_NLSP = 52, SWIPE_DEPRECATED = 53, NARP = 54, MOBILE = 55, TLSP = 56, SKIP = 57, IPV6_ICMP = 58, IPV6_NONXT = 59, IPV6_OPTS = 60, UNKNOWN_61 = 61, CFTP = 62, UNKNOWN_63 = 63, SAT_EXPAK = 64, KRYPTOLAN = 65, RVD = 66, IPPC = 67, UNKNOWN_68 = 68, SAT_MON = 69, VISA = 70, IPCV = 71, CPNX = 72, CPHB = 73, WSN = 74, PVP = 75, BR_SAT_MON = 76, SUN_ND = 77, WB_MON = 78, WB_EXPAK = 79, ISO_IP = 80, VMTP = 81, SECURE_VMTP = 82, VINES = 83, IPTM_OR_TTP = 84, NSFNET_IGP = 85, DGP = 86, TCF = 87, EIGRP = 88, OSPFIGP = 89, SPRITE_RPC = 90, LARP = 91, MTP = 92, AX_25 = 93, IPIP = 94, MICP_DEPRECATED = 95, SCC_SP = 96, ETHERIP = 97, ENCAP = 98, UNKNOWN_99 = 99, GMTP = 100, IFMP = 101, PNNI = 102, PIM = 103, ARIS = 104, SCPS = 105, QNX = 106, A_N = 107, IPCOMP = 108, SNP = 109, COMPAQ_PEER = 110, IPX_IN_IP = 111, VRRP = 112, PGM = 113, UNKNOWN_114 = 114, L2TP = 115, DDX = 116, IATP = 117, STP = 118, SRP = 119, UTI = 120, SMP = 121, SM_DEPRECATED = 122, PTP = 123, ISISOVERIPV4 = 124, FIRE = 125, CRTP = 126, CRUDP = 127, SSCOPMCE = 128, IPLT = 129, SPS = 130, PIPE = 131, SCTP = 132, FC = 133, RSVP_E2E_IGNORE = 134, MOBILITYHEADER = 135, UDPLITE = 136, MPLS_IN_IP = 137, MANET = 138, HIP = 139, SHIM6 = 140, WESP = 141, ROHC = 142, ETHERNET = 143, UNASSIGNED_144 = 144, UNASSIGNED_145 = 145, UNASSIGNED_146 = 146, UNASSIGNED_147 = 147, UNASSIGNED_148 = 148, UNASSIGNED_149 = 149, UNASSIGNED_150 = 150, UNASSIGNED_151 = 151, UNASSIGNED_152 = 152, UNASSIGNED_153 = 153, UNASSIGNED_154 = 154, UNASSIGNED_155 = 155, UNASSIGNED_156 = 156, UNASSIGNED_157 = 157, UNASSIGNED_158 = 158, UNASSIGNED_159 = 159, UNASSIGNED_160 = 160, UNASSIGNED_161 = 161, UNASSIGNED_162 = 162, UNASSIGNED_163 = 163, UNASSIGNED_164 = 164, UNASSIGNED_165 = 165, UNASSIGNED_166 = 166, UNASSIGNED_167 = 167, UNASSIGNED_168 = 168, UNASSIGNED_169 = 169, UNASSIGNED_170 = 170, UNASSIGNED_171 = 171, UNASSIGNED_172 = 172, UNASSIGNED_173 = 173, UNASSIGNED_174 = 174, UNASSIGNED_175 = 175, UNASSIGNED_176 = 176, UNASSIGNED_177 = 177, UNASSIGNED_178 = 178, UNASSIGNED_179 = 179, UNASSIGNED_180 = 180, UNASSIGNED_181 = 181, UNASSIGNED_182 = 182, UNASSIGNED_183 = 183, UNASSIGNED_184 = 184, UNASSIGNED_185 = 185, UNASSIGNED_186 = 186, UNASSIGNED_187 = 187, UNASSIGNED_188 = 188, UNASSIGNED_189 = 189, UNASSIGNED_190 = 190, UNASSIGNED_191 = 191, UNASSIGNED_192 = 192, UNASSIGNED_193 = 193, UNASSIGNED_194 = 194, UNASSIGNED_195 = 195, UNASSIGNED_196 = 196, UNASSIGNED_197 = 197, UNASSIGNED_198 = 198, UNASSIGNED_199 = 199, UNASSIGNED_200 = 200, UNASSIGNED_201 = 201, UNASSIGNED_202 = 202, UNASSIGNED_203 = 203, UNASSIGNED_204 = 204, UNASSIGNED_205 = 205, UNASSIGNED_206 = 206, UNASSIGNED_207 = 207, UNASSIGNED_208 = 208, UNASSIGNED_209 = 209, UNASSIGNED_210 = 210, UNASSIGNED_211 = 211, UNASSIGNED_212 = 212, UNASSIGNED_213 = 213, UNASSIGNED_214 = 214, UNASSIGNED_215 = 215, UNASSIGNED_216 = 216, UNASSIGNED_217 = 217, UNASSIGNED_218 = 218, UNASSIGNED_219 = 219, UNASSIGNED_220 = 220, UNASSIGNED_221 = 221, UNASSIGNED_222 = 222, UNASSIGNED_223 = 223, UNASSIGNED_224 = 224, UNASSIGNED_225 = 225, UNASSIGNED_226 = 226, UNASSIGNED_227 = 227, UNASSIGNED_228 = 228, UNASSIGNED_229 = 229, UNASSIGNED_230 = 230, UNASSIGNED_231 = 231, UNASSIGNED_232 = 232, UNASSIGNED_233 = 233, UNASSIGNED_234 = 234, UNASSIGNED_235 = 235, UNASSIGNED_236 = 236, UNASSIGNED_237 = 237, UNASSIGNED_238 = 238, UNASSIGNED_239 = 239, UNASSIGNED_240 = 240, UNASSIGNED_241 = 241, UNASSIGNED_242 = 242, UNASSIGNED_243 = 243, UNASSIGNED_244 = 244, UNASSIGNED_245 = 245, UNASSIGNED_246 = 246, UNASSIGNED_247 = 247, UNASSIGNED_248 = 248, UNASSIGNED_249 = 249, UNASSIGNED_250 = 250, UNASSIGNED_251 = 251, UNASSIGNED_252 = 252, UNKNOWN_253 = 253, UNKNOWN_254 = 254, RESERVED = 255 }; enum IpProtocolNumberNotTyped : unsigned int { IpProtocolNumberHOPOPT = 0, IpProtocolNumberICMP = 1, IpProtocolNumberIGMP = 2, IpProtocolNumberGGP = 3, IpProtocolNumberIPV4 = 4, IpProtocolNumberST = 5, IpProtocolNumberTCP = 6, IpProtocolNumberCBT = 7, IpProtocolNumberEGP = 8, IpProtocolNumberIGP = 9, IpProtocolNumberBBN_RCC_MON = 10, IpProtocolNumberNVP_II = 11, IpProtocolNumberPUP = 12, IpProtocolNumberARGUS_DEPRECATED = 13, IpProtocolNumberEMCON = 14, IpProtocolNumberXNET = 15, IpProtocolNumberCHAOS = 16, IpProtocolNumberUDP = 17, IpProtocolNumberMUX = 18, IpProtocolNumberDCN_MEAS = 19, IpProtocolNumberHMP = 20, IpProtocolNumberPRM = 21, IpProtocolNumberXNS_IDP = 22, IpProtocolNumberTRUNK_1 = 23, IpProtocolNumberTRUNK_2 = 24, IpProtocolNumberLEAF_1 = 25, IpProtocolNumberLEAF_2 = 26, IpProtocolNumberRDP = 27, IpProtocolNumberIRTP = 28, IpProtocolNumberISO_TP4 = 29, IpProtocolNumberNETBLT = 30, IpProtocolNumberMFE_NSP = 31, IpProtocolNumberMERIT_INP = 32, IpProtocolNumberDCCP = 33, IpProtocolNumberTHREEPC = 34, IpProtocolNumberIDPR = 35, IpProtocolNumberXTP = 36, IpProtocolNumberDDP = 37, IpProtocolNumberIDPR_CMTP = 38, IpProtocolNumberTPPPPP = 39, IpProtocolNumberIL = 40, IpProtocolNumberIPV6 = 41, IpProtocolNumberSDRP = 42, IpProtocolNumberIPV6_ROUTE = 43, IpProtocolNumberIPV6_FRAG = 44, IpProtocolNumberIDRP = 45, IpProtocolNumberRSVP = 46, IpProtocolNumberGRE = 47, IpProtocolNumberDSR = 48, IpProtocolNumberBNA = 49, IpProtocolNumberESP = 50, IpProtocolNumberAH = 51, IpProtocolNumberI_NLSP = 52, IpProtocolNumberSWIPE_DEPRECATED = 53, IpProtocolNumberNARP = 54, IpProtocolNumberMOBILE = 55, IpProtocolNumberTLSP = 56, IpProtocolNumberSKIP = 57, IpProtocolNumberIPV6_ICMP = 58, IpProtocolNumberIPV6_NONXT = 59, IpProtocolNumberIPV6_OPTS = 60, IpProtocolNumberUNKNOWN_61 = 61, IpProtocolNumberCFTP = 62, IpProtocolNumberUNKNOWN_63 = 63, IpProtocolNumberSAT_EXPAK = 64, IpProtocolNumberKRYPTOLAN = 65, IpProtocolNumberRVD = 66, IpProtocolNumberIPPC = 67, IpProtocolNumberUNKNOWN_68 = 68, IpProtocolNumberSAT_MON = 69, IpProtocolNumberVISA = 70, IpProtocolNumberIPCV = 71, IpProtocolNumberCPNX = 72, IpProtocolNumberCPHB = 73, IpProtocolNumberWSN = 74, IpProtocolNumberPVP = 75, IpProtocolNumberBR_SAT_MON = 76, IpProtocolNumberSUN_ND = 77, IpProtocolNumberWB_MON = 78, IpProtocolNumberWB_EXPAK = 79, IpProtocolNumberISO_IP = 80, IpProtocolNumberVMTP = 81, IpProtocolNumberSECURE_VMTP = 82, IpProtocolNumberVINES = 83, IpProtocolNumberIPTM_OR_TTP = 84, IpProtocolNumberNSFNET_IGP = 85, IpProtocolNumberDGP = 86, IpProtocolNumberTCF = 87, IpProtocolNumberEIGRP = 88, IpProtocolNumberOSPFIGP = 89, IpProtocolNumberSPRITE_RPC = 90, IpProtocolNumberLARP = 91, IpProtocolNumberMTP = 92, IpProtocolNumberAX_25 = 93, IpProtocolNumberIPIP = 94, IpProtocolNumberMICP_DEPRECATED = 95, IpProtocolNumberSCC_SP = 96, IpProtocolNumberETHERIP = 97, IpProtocolNumberENCAP = 98, IpProtocolNumberUNKNOWN_99 = 99, IpProtocolNumberGMTP = 100, IpProtocolNumberIFMP = 101, IpProtocolNumberPNNI = 102, IpProtocolNumberPIM = 103, IpProtocolNumberARIS = 104, IpProtocolNumberSCPS = 105, IpProtocolNumberQNX = 106, IpProtocolNumberA_N = 107, IpProtocolNumberIPCOMP = 108, IpProtocolNumberSNP = 109, IpProtocolNumberCOMPAQ_PEER = 110, IpProtocolNumberIPX_IN_IP = 111, IpProtocolNumberVRRP = 112, IpProtocolNumberPGM = 113, IpProtocolNumberUNKNOWN_114 = 114, IpProtocolNumberL2TP = 115, IpProtocolNumberDDX = 116, IpProtocolNumberIATP = 117, IpProtocolNumberSTP = 118, IpProtocolNumberSRP = 119, IpProtocolNumberUTI = 120, IpProtocolNumberSMP = 121, IpProtocolNumberSM_DEPRECATED = 122, IpProtocolNumberPTP = 123, IpProtocolNumberISISOVERIPV4 = 124, IpProtocolNumberFIRE = 125, IpProtocolNumberCRTP = 126, IpProtocolNumberCRUDP = 127, IpProtocolNumberSSCOPMCE = 128, IpProtocolNumberIPLT = 129, IpProtocolNumberSPS = 130, IpProtocolNumberPIPE = 131, IpProtocolNumberSCTP = 132, IpProtocolNumberFC = 133, IpProtocolNumberRSVP_E2E_IGNORE = 134, IpProtocolNumberMOBILITYHEADER = 135, IpProtocolNumberUDPLITE = 136, IpProtocolNumberMPLS_IN_IP = 137, IpProtocolNumberMANET = 138, IpProtocolNumberHIP = 139, IpProtocolNumberSHIM6 = 140, IpProtocolNumberWESP = 141, IpProtocolNumberROHC = 142, IpProtocolNumberETHERNET = 143, IpProtocolNumberUNASSIGNED_144 = 144, IpProtocolNumberUNASSIGNED_145 = 145, IpProtocolNumberUNASSIGNED_146 = 146, IpProtocolNumberUNASSIGNED_147 = 147, IpProtocolNumberUNASSIGNED_148 = 148, IpProtocolNumberUNASSIGNED_149 = 149, IpProtocolNumberUNASSIGNED_150 = 150, IpProtocolNumberUNASSIGNED_151 = 151, IpProtocolNumberUNASSIGNED_152 = 152, IpProtocolNumberUNASSIGNED_153 = 153, IpProtocolNumberUNASSIGNED_154 = 154, IpProtocolNumberUNASSIGNED_155 = 155, IpProtocolNumberUNASSIGNED_156 = 156, IpProtocolNumberUNASSIGNED_157 = 157, IpProtocolNumberUNASSIGNED_158 = 158, IpProtocolNumberUNASSIGNED_159 = 159, IpProtocolNumberUNASSIGNED_160 = 160, IpProtocolNumberUNASSIGNED_161 = 161, IpProtocolNumberUNASSIGNED_162 = 162, IpProtocolNumberUNASSIGNED_163 = 163, IpProtocolNumberUNASSIGNED_164 = 164, IpProtocolNumberUNASSIGNED_165 = 165, IpProtocolNumberUNASSIGNED_166 = 166, IpProtocolNumberUNASSIGNED_167 = 167, IpProtocolNumberUNASSIGNED_168 = 168, IpProtocolNumberUNASSIGNED_169 = 169, IpProtocolNumberUNASSIGNED_170 = 170, IpProtocolNumberUNASSIGNED_171 = 171, IpProtocolNumberUNASSIGNED_172 = 172, IpProtocolNumberUNASSIGNED_173 = 173, IpProtocolNumberUNASSIGNED_174 = 174, IpProtocolNumberUNASSIGNED_175 = 175, IpProtocolNumberUNASSIGNED_176 = 176, IpProtocolNumberUNASSIGNED_177 = 177, IpProtocolNumberUNASSIGNED_178 = 178, IpProtocolNumberUNASSIGNED_179 = 179, IpProtocolNumberUNASSIGNED_180 = 180, IpProtocolNumberUNASSIGNED_181 = 181, IpProtocolNumberUNASSIGNED_182 = 182, IpProtocolNumberUNASSIGNED_183 = 183, IpProtocolNumberUNASSIGNED_184 = 184, IpProtocolNumberUNASSIGNED_185 = 185, IpProtocolNumberUNASSIGNED_186 = 186, IpProtocolNumberUNASSIGNED_187 = 187, IpProtocolNumberUNASSIGNED_188 = 188, IpProtocolNumberUNASSIGNED_189 = 189, IpProtocolNumberUNASSIGNED_190 = 190, IpProtocolNumberUNASSIGNED_191 = 191, IpProtocolNumberUNASSIGNED_192 = 192, IpProtocolNumberUNASSIGNED_193 = 193, IpProtocolNumberUNASSIGNED_194 = 194, IpProtocolNumberUNASSIGNED_195 = 195, IpProtocolNumberUNASSIGNED_196 = 196, IpProtocolNumberUNASSIGNED_197 = 197, IpProtocolNumberUNASSIGNED_198 = 198, IpProtocolNumberUNASSIGNED_199 = 199, IpProtocolNumberUNASSIGNED_200 = 200, IpProtocolNumberUNASSIGNED_201 = 201, IpProtocolNumberUNASSIGNED_202 = 202, IpProtocolNumberUNASSIGNED_203 = 203, IpProtocolNumberUNASSIGNED_204 = 204, IpProtocolNumberUNASSIGNED_205 = 205, IpProtocolNumberUNASSIGNED_206 = 206, IpProtocolNumberUNASSIGNED_207 = 207, IpProtocolNumberUNASSIGNED_208 = 208, IpProtocolNumberUNASSIGNED_209 = 209, IpProtocolNumberUNASSIGNED_210 = 210, IpProtocolNumberUNASSIGNED_211 = 211, IpProtocolNumberUNASSIGNED_212 = 212, IpProtocolNumberUNASSIGNED_213 = 213, IpProtocolNumberUNASSIGNED_214 = 214, IpProtocolNumberUNASSIGNED_215 = 215, IpProtocolNumberUNASSIGNED_216 = 216, IpProtocolNumberUNASSIGNED_217 = 217, IpProtocolNumberUNASSIGNED_218 = 218, IpProtocolNumberUNASSIGNED_219 = 219, IpProtocolNumberUNASSIGNED_220 = 220, IpProtocolNumberUNASSIGNED_221 = 221, IpProtocolNumberUNASSIGNED_222 = 222, IpProtocolNumberUNASSIGNED_223 = 223, IpProtocolNumberUNASSIGNED_224 = 224, IpProtocolNumberUNASSIGNED_225 = 225, IpProtocolNumberUNASSIGNED_226 = 226, IpProtocolNumberUNASSIGNED_227 = 227, IpProtocolNumberUNASSIGNED_228 = 228, IpProtocolNumberUNASSIGNED_229 = 229, IpProtocolNumberUNASSIGNED_230 = 230, IpProtocolNumberUNASSIGNED_231 = 231, IpProtocolNumberUNASSIGNED_232 = 232, IpProtocolNumberUNASSIGNED_233 = 233, IpProtocolNumberUNASSIGNED_234 = 234, IpProtocolNumberUNASSIGNED_235 = 235, IpProtocolNumberUNASSIGNED_236 = 236, IpProtocolNumberUNASSIGNED_237 = 237, IpProtocolNumberUNASSIGNED_238 = 238, IpProtocolNumberUNASSIGNED_239 = 239, IpProtocolNumberUNASSIGNED_240 = 240, IpProtocolNumberUNASSIGNED_241 = 241, IpProtocolNumberUNASSIGNED_242 = 242, IpProtocolNumberUNASSIGNED_243 = 243, IpProtocolNumberUNASSIGNED_244 = 244, IpProtocolNumberUNASSIGNED_245 = 245, IpProtocolNumberUNASSIGNED_246 = 246, IpProtocolNumberUNASSIGNED_247 = 247, IpProtocolNumberUNASSIGNED_248 = 248, IpProtocolNumberUNASSIGNED_249 = 249, IpProtocolNumberUNASSIGNED_250 = 250, IpProtocolNumberUNASSIGNED_251 = 251, IpProtocolNumberUNASSIGNED_252 = 252, IpProtocolNumberUNKNOWN_253 = 253, IpProtocolNumberUNKNOWN_254 = 254, IpProtocolNumberRESERVED = 255, }; ip_protocol_t get_ip_protocol_enum_type_from_integer(uint8_t protocol_as_integer); uint8_t get_ip_protocol_enum_as_number(ip_protocol_t ip_protocol_enum); const char* get_ip_protocol_name(ip_protocol_t protocol); bool read_protocol_from_string(const std::string& protocol_string, ip_protocol_t& ip_protocol_enum); pavel-odintsov-fastnetmon-394fbe0/src/iana_ip_protocols.cpp000066400000000000000000002062151520703010000242470ustar00rootroot00000000000000#include "iana/iana_ip_protocols.hpp" #include #include const char* get_ip_protocol_name_by_number_iana(uint8_t protocol_number) { switch (protocol_number) { case 0: return "HOPOPT"; break; case 1: return "ICMP"; break; case 2: return "IGMP"; break; case 3: return "GGP"; break; case 4: return "IPV4"; break; case 5: return "ST"; break; case 6: return "TCP"; break; case 7: return "CBT"; break; case 8: return "EGP"; break; case 9: return "IGP"; break; case 10: return "BBN_RCC_MON"; break; case 11: return "NVP_II"; break; case 12: return "PUP"; break; case 13: return "ARGUS_DEPRECATED"; break; case 14: return "EMCON"; break; case 15: return "XNET"; break; case 16: return "CHAOS"; break; case 17: return "UDP"; break; case 18: return "MUX"; break; case 19: return "DCN_MEAS"; break; case 20: return "HMP"; break; case 21: return "PRM"; break; case 22: return "XNS_IDP"; break; case 23: return "TRUNK_1"; break; case 24: return "TRUNK_2"; break; case 25: return "LEAF_1"; break; case 26: return "LEAF_2"; break; case 27: return "RDP"; break; case 28: return "IRTP"; break; case 29: return "ISO_TP4"; break; case 30: return "NETBLT"; break; case 31: return "MFE_NSP"; break; case 32: return "MERIT_INP"; break; case 33: return "DCCP"; break; case 34: return "THREEPC"; break; case 35: return "IDPR"; break; case 36: return "XTP"; break; case 37: return "DDP"; break; case 38: return "IDPR_CMTP"; break; case 39: return "TPPPPP"; break; case 40: return "IL"; break; case 41: return "IPV6"; break; case 42: return "SDRP"; break; case 43: return "IPV6_ROUTE"; break; case 44: return "IPV6_FRAG"; break; case 45: return "IDRP"; break; case 46: return "RSVP"; break; case 47: return "GRE"; break; case 48: return "DSR"; break; case 49: return "BNA"; break; case 50: return "ESP"; break; case 51: return "AH"; break; case 52: return "I_NLSP"; break; case 53: return "SWIPE_DEPRECATED"; break; case 54: return "NARP"; break; case 55: return "MOBILE"; break; case 56: return "TLSP"; break; case 57: return "SKIP"; break; case 58: return "IPV6_ICMP"; break; case 59: return "IPV6_NONXT"; break; case 60: return "IPV6_OPTS"; break; case 61: return "UNKNOWN_61"; break; case 62: return "CFTP"; break; case 63: return "UNKNOWN_63"; break; case 64: return "SAT_EXPAK"; break; case 65: return "KRYPTOLAN"; break; case 66: return "RVD"; break; case 67: return "IPPC"; break; case 68: return "UNKNOWN_68"; break; case 69: return "SAT_MON"; break; case 70: return "VISA"; break; case 71: return "IPCV"; break; case 72: return "CPNX"; break; case 73: return "CPHB"; break; case 74: return "WSN"; break; case 75: return "PVP"; break; case 76: return "BR_SAT_MON"; break; case 77: return "SUN_ND"; break; case 78: return "WB_MON"; break; case 79: return "WB_EXPAK"; break; case 80: return "ISO_IP"; break; case 81: return "VMTP"; break; case 82: return "SECURE_VMTP"; break; case 83: return "VINES"; break; case 84: return "IPTM_OR_TTP"; break; case 85: return "NSFNET_IGP"; break; case 86: return "DGP"; break; case 87: return "TCF"; break; case 88: return "EIGRP"; break; case 89: return "OSPFIGP"; break; case 90: return "SPRITE_RPC"; break; case 91: return "LARP"; break; case 92: return "MTP"; break; case 93: return "AX_25"; break; case 94: return "IPIP"; break; case 95: return "MICP_DEPRECATED"; break; case 96: return "SCC_SP"; break; case 97: return "ETHERIP"; break; case 98: return "ENCAP"; break; case 99: return "UNKNOWN_99"; break; case 100: return "GMTP"; break; case 101: return "IFMP"; break; case 102: return "PNNI"; break; case 103: return "PIM"; break; case 104: return "ARIS"; break; case 105: return "SCPS"; break; case 106: return "QNX"; break; case 107: return "A_N"; break; case 108: return "IPCOMP"; break; case 109: return "SNP"; break; case 110: return "COMPAQ_PEER"; break; case 111: return "IPX_IN_IP"; break; case 112: return "VRRP"; break; case 113: return "PGM"; break; case 114: return "UNKNOWN_114"; break; case 115: return "L2TP"; break; case 116: return "DDX"; break; case 117: return "IATP"; break; case 118: return "STP"; break; case 119: return "SRP"; break; case 120: return "UTI"; break; case 121: return "SMP"; break; case 122: return "SM_DEPRECATED"; break; case 123: return "PTP"; break; case 124: return "ISISOVERIPV4"; break; case 125: return "FIRE"; break; case 126: return "CRTP"; break; case 127: return "CRUDP"; break; case 128: return "SSCOPMCE"; break; case 129: return "IPLT"; break; case 130: return "SPS"; break; case 131: return "PIPE"; break; case 132: return "SCTP"; break; case 133: return "FC"; break; case 134: return "RSVP_E2E_IGNORE"; break; case 135: return "MOBILITYHEADER"; break; case 136: return "UDPLITE"; break; case 137: return "MPLS_IN_IP"; break; case 138: return "MANET"; break; case 139: return "HIP"; break; case 140: return "SHIM6"; break; case 141: return "WESP"; break; case 142: return "ROHC"; break; case 143: return "ETHERNET"; break; case 144: return "UNASSIGNED_144"; break; case 145: return "UNASSIGNED_145"; break; case 146: return "UNASSIGNED_146"; break; case 147: return "UNASSIGNED_147"; break; case 148: return "UNASSIGNED_148"; break; case 149: return "UNASSIGNED_149"; break; case 150: return "UNASSIGNED_150"; break; case 151: return "UNASSIGNED_151"; break; case 152: return "UNASSIGNED_152"; break; case 153: return "UNASSIGNED_153"; break; case 154: return "UNASSIGNED_154"; break; case 155: return "UNASSIGNED_155"; break; case 156: return "UNASSIGNED_156"; break; case 157: return "UNASSIGNED_157"; break; case 158: return "UNASSIGNED_158"; break; case 159: return "UNASSIGNED_159"; break; case 160: return "UNASSIGNED_160"; break; case 161: return "UNASSIGNED_161"; break; case 162: return "UNASSIGNED_162"; break; case 163: return "UNASSIGNED_163"; break; case 164: return "UNASSIGNED_164"; break; case 165: return "UNASSIGNED_165"; break; case 166: return "UNASSIGNED_166"; break; case 167: return "UNASSIGNED_167"; break; case 168: return "UNASSIGNED_168"; break; case 169: return "UNASSIGNED_169"; break; case 170: return "UNASSIGNED_170"; break; case 171: return "UNASSIGNED_171"; break; case 172: return "UNASSIGNED_172"; break; case 173: return "UNASSIGNED_173"; break; case 174: return "UNASSIGNED_174"; break; case 175: return "UNASSIGNED_175"; break; case 176: return "UNASSIGNED_176"; break; case 177: return "UNASSIGNED_177"; break; case 178: return "UNASSIGNED_178"; break; case 179: return "UNASSIGNED_179"; break; case 180: return "UNASSIGNED_180"; break; case 181: return "UNASSIGNED_181"; break; case 182: return "UNASSIGNED_182"; break; case 183: return "UNASSIGNED_183"; break; case 184: return "UNASSIGNED_184"; break; case 185: return "UNASSIGNED_185"; break; case 186: return "UNASSIGNED_186"; break; case 187: return "UNASSIGNED_187"; break; case 188: return "UNASSIGNED_188"; break; case 189: return "UNASSIGNED_189"; break; case 190: return "UNASSIGNED_190"; break; case 191: return "UNASSIGNED_191"; break; case 192: return "UNASSIGNED_192"; break; case 193: return "UNASSIGNED_193"; break; case 194: return "UNASSIGNED_194"; break; case 195: return "UNASSIGNED_195"; break; case 196: return "UNASSIGNED_196"; break; case 197: return "UNASSIGNED_197"; break; case 198: return "UNASSIGNED_198"; break; case 199: return "UNASSIGNED_199"; break; case 200: return "UNASSIGNED_200"; break; case 201: return "UNASSIGNED_201"; break; case 202: return "UNASSIGNED_202"; break; case 203: return "UNASSIGNED_203"; break; case 204: return "UNASSIGNED_204"; break; case 205: return "UNASSIGNED_205"; break; case 206: return "UNASSIGNED_206"; break; case 207: return "UNASSIGNED_207"; break; case 208: return "UNASSIGNED_208"; break; case 209: return "UNASSIGNED_209"; break; case 210: return "UNASSIGNED_210"; break; case 211: return "UNASSIGNED_211"; break; case 212: return "UNASSIGNED_212"; break; case 213: return "UNASSIGNED_213"; break; case 214: return "UNASSIGNED_214"; break; case 215: return "UNASSIGNED_215"; break; case 216: return "UNASSIGNED_216"; break; case 217: return "UNASSIGNED_217"; break; case 218: return "UNASSIGNED_218"; break; case 219: return "UNASSIGNED_219"; break; case 220: return "UNASSIGNED_220"; break; case 221: return "UNASSIGNED_221"; break; case 222: return "UNASSIGNED_222"; break; case 223: return "UNASSIGNED_223"; break; case 224: return "UNASSIGNED_224"; break; case 225: return "UNASSIGNED_225"; break; case 226: return "UNASSIGNED_226"; break; case 227: return "UNASSIGNED_227"; break; case 228: return "UNASSIGNED_228"; break; case 229: return "UNASSIGNED_229"; break; case 230: return "UNASSIGNED_230"; break; case 231: return "UNASSIGNED_231"; break; case 232: return "UNASSIGNED_232"; break; case 233: return "UNASSIGNED_233"; break; case 234: return "UNASSIGNED_234"; break; case 235: return "UNASSIGNED_235"; break; case 236: return "UNASSIGNED_236"; break; case 237: return "UNASSIGNED_237"; break; case 238: return "UNASSIGNED_238"; break; case 239: return "UNASSIGNED_239"; break; case 240: return "UNASSIGNED_240"; break; case 241: return "UNASSIGNED_241"; break; case 242: return "UNASSIGNED_242"; break; case 243: return "UNASSIGNED_243"; break; case 244: return "UNASSIGNED_244"; break; case 245: return "UNASSIGNED_245"; break; case 246: return "UNASSIGNED_246"; break; case 247: return "UNASSIGNED_247"; break; case 248: return "UNASSIGNED_248"; break; case 249: return "UNASSIGNED_249"; break; case 250: return "UNASSIGNED_250"; break; case 251: return "UNASSIGNED_251"; break; case 252: return "UNASSIGNED_252"; break; case 253: return "UNKNOWN_253"; break; case 254: return "UNKNOWN_254"; break; case 255: return "RESERVED"; break; } } const char* get_ip_protocol_name(ip_protocol_t protocol) { switch (protocol) { case ip_protocol_t::HOPOPT: return "HOPOPT"; break; case ip_protocol_t::ICMP: return "ICMP"; break; case ip_protocol_t::IGMP: return "IGMP"; break; case ip_protocol_t::GGP: return "GGP"; break; case ip_protocol_t::IPV4: return "IPV4"; break; case ip_protocol_t::ST: return "ST"; break; case ip_protocol_t::TCP: return "TCP"; break; case ip_protocol_t::CBT: return "CBT"; break; case ip_protocol_t::EGP: return "EGP"; break; case ip_protocol_t::IGP: return "IGP"; break; case ip_protocol_t::BBN_RCC_MON: return "BBN_RCC_MON"; break; case ip_protocol_t::NVP_II: return "NVP_II"; break; case ip_protocol_t::PUP: return "PUP"; break; case ip_protocol_t::ARGUS_DEPRECATED: return "ARGUS_DEPRECATED"; break; case ip_protocol_t::EMCON: return "EMCON"; break; case ip_protocol_t::XNET: return "XNET"; break; case ip_protocol_t::CHAOS: return "CHAOS"; break; case ip_protocol_t::UDP: return "UDP"; break; case ip_protocol_t::MUX: return "MUX"; break; case ip_protocol_t::DCN_MEAS: return "DCN_MEAS"; break; case ip_protocol_t::HMP: return "HMP"; break; case ip_protocol_t::PRM: return "PRM"; break; case ip_protocol_t::XNS_IDP: return "XNS_IDP"; break; case ip_protocol_t::TRUNK_1: return "TRUNK_1"; break; case ip_protocol_t::TRUNK_2: return "TRUNK_2"; break; case ip_protocol_t::LEAF_1: return "LEAF_1"; break; case ip_protocol_t::LEAF_2: return "LEAF_2"; break; case ip_protocol_t::RDP: return "RDP"; break; case ip_protocol_t::IRTP: return "IRTP"; break; case ip_protocol_t::ISO_TP4: return "ISO_TP4"; break; case ip_protocol_t::NETBLT: return "NETBLT"; break; case ip_protocol_t::MFE_NSP: return "MFE_NSP"; break; case ip_protocol_t::MERIT_INP: return "MERIT_INP"; break; case ip_protocol_t::DCCP: return "DCCP"; break; case ip_protocol_t::THREEPC: return "THREEPC"; break; case ip_protocol_t::IDPR: return "IDPR"; break; case ip_protocol_t::XTP: return "XTP"; break; case ip_protocol_t::DDP: return "DDP"; break; case ip_protocol_t::IDPR_CMTP: return "IDPR_CMTP"; break; case ip_protocol_t::TPPPPP: return "TPPPPP"; break; case ip_protocol_t::IL: return "IL"; break; case ip_protocol_t::IPV6: return "IPV6"; break; case ip_protocol_t::SDRP: return "SDRP"; break; case ip_protocol_t::IPV6_ROUTE: return "IPV6_ROUTE"; break; case ip_protocol_t::IPV6_FRAG: return "IPV6_FRAG"; break; case ip_protocol_t::IDRP: return "IDRP"; break; case ip_protocol_t::RSVP: return "RSVP"; break; case ip_protocol_t::GRE: return "GRE"; break; case ip_protocol_t::DSR: return "DSR"; break; case ip_protocol_t::BNA: return "BNA"; break; case ip_protocol_t::ESP: return "ESP"; break; case ip_protocol_t::AH: return "AH"; break; case ip_protocol_t::I_NLSP: return "I_NLSP"; break; case ip_protocol_t::SWIPE_DEPRECATED: return "SWIPE_DEPRECATED"; break; case ip_protocol_t::NARP: return "NARP"; break; case ip_protocol_t::MOBILE: return "MOBILE"; break; case ip_protocol_t::TLSP: return "TLSP"; break; case ip_protocol_t::SKIP: return "SKIP"; break; case ip_protocol_t::IPV6_ICMP: return "IPV6_ICMP"; break; case ip_protocol_t::IPV6_NONXT: return "IPV6_NONXT"; break; case ip_protocol_t::IPV6_OPTS: return "IPV6_OPTS"; break; case ip_protocol_t::UNKNOWN_61: return "UNKNOWN_61"; break; case ip_protocol_t::CFTP: return "CFTP"; break; case ip_protocol_t::UNKNOWN_63: return "UNKNOWN_63"; break; case ip_protocol_t::SAT_EXPAK: return "SAT_EXPAK"; break; case ip_protocol_t::KRYPTOLAN: return "KRYPTOLAN"; break; case ip_protocol_t::RVD: return "RVD"; break; case ip_protocol_t::IPPC: return "IPPC"; break; case ip_protocol_t::UNKNOWN_68: return "UNKNOWN_68"; break; case ip_protocol_t::SAT_MON: return "SAT_MON"; break; case ip_protocol_t::VISA: return "VISA"; break; case ip_protocol_t::IPCV: return "IPCV"; break; case ip_protocol_t::CPNX: return "CPNX"; break; case ip_protocol_t::CPHB: return "CPHB"; break; case ip_protocol_t::WSN: return "WSN"; break; case ip_protocol_t::PVP: return "PVP"; break; case ip_protocol_t::BR_SAT_MON: return "BR_SAT_MON"; break; case ip_protocol_t::SUN_ND: return "SUN_ND"; break; case ip_protocol_t::WB_MON: return "WB_MON"; break; case ip_protocol_t::WB_EXPAK: return "WB_EXPAK"; break; case ip_protocol_t::ISO_IP: return "ISO_IP"; break; case ip_protocol_t::VMTP: return "VMTP"; break; case ip_protocol_t::SECURE_VMTP: return "SECURE_VMTP"; break; case ip_protocol_t::VINES: return "VINES"; break; case ip_protocol_t::IPTM_OR_TTP: return "IPTM_OR_TTP"; break; case ip_protocol_t::NSFNET_IGP: return "NSFNET_IGP"; break; case ip_protocol_t::DGP: return "DGP"; break; case ip_protocol_t::TCF: return "TCF"; break; case ip_protocol_t::EIGRP: return "EIGRP"; break; case ip_protocol_t::OSPFIGP: return "OSPFIGP"; break; case ip_protocol_t::SPRITE_RPC: return "SPRITE_RPC"; break; case ip_protocol_t::LARP: return "LARP"; break; case ip_protocol_t::MTP: return "MTP"; break; case ip_protocol_t::AX_25: return "AX_25"; break; case ip_protocol_t::IPIP: return "IPIP"; break; case ip_protocol_t::MICP_DEPRECATED: return "MICP_DEPRECATED"; break; case ip_protocol_t::SCC_SP: return "SCC_SP"; break; case ip_protocol_t::ETHERIP: return "ETHERIP"; break; case ip_protocol_t::ENCAP: return "ENCAP"; break; case ip_protocol_t::UNKNOWN_99: return "UNKNOWN_99"; break; case ip_protocol_t::GMTP: return "GMTP"; break; case ip_protocol_t::IFMP: return "IFMP"; break; case ip_protocol_t::PNNI: return "PNNI"; break; case ip_protocol_t::PIM: return "PIM"; break; case ip_protocol_t::ARIS: return "ARIS"; break; case ip_protocol_t::SCPS: return "SCPS"; break; case ip_protocol_t::QNX: return "QNX"; break; case ip_protocol_t::A_N: return "A_N"; break; case ip_protocol_t::IPCOMP: return "IPCOMP"; break; case ip_protocol_t::SNP: return "SNP"; break; case ip_protocol_t::COMPAQ_PEER: return "COMPAQ_PEER"; break; case ip_protocol_t::IPX_IN_IP: return "IPX_IN_IP"; break; case ip_protocol_t::VRRP: return "VRRP"; break; case ip_protocol_t::PGM: return "PGM"; break; case ip_protocol_t::UNKNOWN_114: return "UNKNOWN_114"; break; case ip_protocol_t::L2TP: return "L2TP"; break; case ip_protocol_t::DDX: return "DDX"; break; case ip_protocol_t::IATP: return "IATP"; break; case ip_protocol_t::STP: return "STP"; break; case ip_protocol_t::SRP: return "SRP"; break; case ip_protocol_t::UTI: return "UTI"; break; case ip_protocol_t::SMP: return "SMP"; break; case ip_protocol_t::SM_DEPRECATED: return "SM_DEPRECATED"; break; case ip_protocol_t::PTP: return "PTP"; break; case ip_protocol_t::ISISOVERIPV4: return "ISISOVERIPV4"; break; case ip_protocol_t::FIRE: return "FIRE"; break; case ip_protocol_t::CRTP: return "CRTP"; break; case ip_protocol_t::CRUDP: return "CRUDP"; break; case ip_protocol_t::SSCOPMCE: return "SSCOPMCE"; break; case ip_protocol_t::IPLT: return "IPLT"; break; case ip_protocol_t::SPS: return "SPS"; break; case ip_protocol_t::PIPE: return "PIPE"; break; case ip_protocol_t::SCTP: return "SCTP"; break; case ip_protocol_t::FC: return "FC"; break; case ip_protocol_t::RSVP_E2E_IGNORE: return "RSVP_E2E_IGNORE"; break; case ip_protocol_t::MOBILITYHEADER: return "MOBILITYHEADER"; break; case ip_protocol_t::UDPLITE: return "UDPLITE"; break; case ip_protocol_t::MPLS_IN_IP: return "MPLS_IN_IP"; break; case ip_protocol_t::MANET: return "MANET"; break; case ip_protocol_t::HIP: return "HIP"; break; case ip_protocol_t::SHIM6: return "SHIM6"; break; case ip_protocol_t::WESP: return "WESP"; break; case ip_protocol_t::ROHC: return "ROHC"; break; case ip_protocol_t::ETHERNET: return "ETHERNET"; break; case ip_protocol_t::UNASSIGNED_144: return "UNASSIGNED_144"; break; case ip_protocol_t::UNASSIGNED_145: return "UNASSIGNED_145"; break; case ip_protocol_t::UNASSIGNED_146: return "UNASSIGNED_146"; break; case ip_protocol_t::UNASSIGNED_147: return "UNASSIGNED_147"; break; case ip_protocol_t::UNASSIGNED_148: return "UNASSIGNED_148"; break; case ip_protocol_t::UNASSIGNED_149: return "UNASSIGNED_149"; break; case ip_protocol_t::UNASSIGNED_150: return "UNASSIGNED_150"; break; case ip_protocol_t::UNASSIGNED_151: return "UNASSIGNED_151"; break; case ip_protocol_t::UNASSIGNED_152: return "UNASSIGNED_152"; break; case ip_protocol_t::UNASSIGNED_153: return "UNASSIGNED_153"; break; case ip_protocol_t::UNASSIGNED_154: return "UNASSIGNED_154"; break; case ip_protocol_t::UNASSIGNED_155: return "UNASSIGNED_155"; break; case ip_protocol_t::UNASSIGNED_156: return "UNASSIGNED_156"; break; case ip_protocol_t::UNASSIGNED_157: return "UNASSIGNED_157"; break; case ip_protocol_t::UNASSIGNED_158: return "UNASSIGNED_158"; break; case ip_protocol_t::UNASSIGNED_159: return "UNASSIGNED_159"; break; case ip_protocol_t::UNASSIGNED_160: return "UNASSIGNED_160"; break; case ip_protocol_t::UNASSIGNED_161: return "UNASSIGNED_161"; break; case ip_protocol_t::UNASSIGNED_162: return "UNASSIGNED_162"; break; case ip_protocol_t::UNASSIGNED_163: return "UNASSIGNED_163"; break; case ip_protocol_t::UNASSIGNED_164: return "UNASSIGNED_164"; break; case ip_protocol_t::UNASSIGNED_165: return "UNASSIGNED_165"; break; case ip_protocol_t::UNASSIGNED_166: return "UNASSIGNED_166"; break; case ip_protocol_t::UNASSIGNED_167: return "UNASSIGNED_167"; break; case ip_protocol_t::UNASSIGNED_168: return "UNASSIGNED_168"; break; case ip_protocol_t::UNASSIGNED_169: return "UNASSIGNED_169"; break; case ip_protocol_t::UNASSIGNED_170: return "UNASSIGNED_170"; break; case ip_protocol_t::UNASSIGNED_171: return "UNASSIGNED_171"; break; case ip_protocol_t::UNASSIGNED_172: return "UNASSIGNED_172"; break; case ip_protocol_t::UNASSIGNED_173: return "UNASSIGNED_173"; break; case ip_protocol_t::UNASSIGNED_174: return "UNASSIGNED_174"; break; case ip_protocol_t::UNASSIGNED_175: return "UNASSIGNED_175"; break; case ip_protocol_t::UNASSIGNED_176: return "UNASSIGNED_176"; break; case ip_protocol_t::UNASSIGNED_177: return "UNASSIGNED_177"; break; case ip_protocol_t::UNASSIGNED_178: return "UNASSIGNED_178"; break; case ip_protocol_t::UNASSIGNED_179: return "UNASSIGNED_179"; break; case ip_protocol_t::UNASSIGNED_180: return "UNASSIGNED_180"; break; case ip_protocol_t::UNASSIGNED_181: return "UNASSIGNED_181"; break; case ip_protocol_t::UNASSIGNED_182: return "UNASSIGNED_182"; break; case ip_protocol_t::UNASSIGNED_183: return "UNASSIGNED_183"; break; case ip_protocol_t::UNASSIGNED_184: return "UNASSIGNED_184"; break; case ip_protocol_t::UNASSIGNED_185: return "UNASSIGNED_185"; break; case ip_protocol_t::UNASSIGNED_186: return "UNASSIGNED_186"; break; case ip_protocol_t::UNASSIGNED_187: return "UNASSIGNED_187"; break; case ip_protocol_t::UNASSIGNED_188: return "UNASSIGNED_188"; break; case ip_protocol_t::UNASSIGNED_189: return "UNASSIGNED_189"; break; case ip_protocol_t::UNASSIGNED_190: return "UNASSIGNED_190"; break; case ip_protocol_t::UNASSIGNED_191: return "UNASSIGNED_191"; break; case ip_protocol_t::UNASSIGNED_192: return "UNASSIGNED_192"; break; case ip_protocol_t::UNASSIGNED_193: return "UNASSIGNED_193"; break; case ip_protocol_t::UNASSIGNED_194: return "UNASSIGNED_194"; break; case ip_protocol_t::UNASSIGNED_195: return "UNASSIGNED_195"; break; case ip_protocol_t::UNASSIGNED_196: return "UNASSIGNED_196"; break; case ip_protocol_t::UNASSIGNED_197: return "UNASSIGNED_197"; break; case ip_protocol_t::UNASSIGNED_198: return "UNASSIGNED_198"; break; case ip_protocol_t::UNASSIGNED_199: return "UNASSIGNED_199"; break; case ip_protocol_t::UNASSIGNED_200: return "UNASSIGNED_200"; break; case ip_protocol_t::UNASSIGNED_201: return "UNASSIGNED_201"; break; case ip_protocol_t::UNASSIGNED_202: return "UNASSIGNED_202"; break; case ip_protocol_t::UNASSIGNED_203: return "UNASSIGNED_203"; break; case ip_protocol_t::UNASSIGNED_204: return "UNASSIGNED_204"; break; case ip_protocol_t::UNASSIGNED_205: return "UNASSIGNED_205"; break; case ip_protocol_t::UNASSIGNED_206: return "UNASSIGNED_206"; break; case ip_protocol_t::UNASSIGNED_207: return "UNASSIGNED_207"; break; case ip_protocol_t::UNASSIGNED_208: return "UNASSIGNED_208"; break; case ip_protocol_t::UNASSIGNED_209: return "UNASSIGNED_209"; break; case ip_protocol_t::UNASSIGNED_210: return "UNASSIGNED_210"; break; case ip_protocol_t::UNASSIGNED_211: return "UNASSIGNED_211"; break; case ip_protocol_t::UNASSIGNED_212: return "UNASSIGNED_212"; break; case ip_protocol_t::UNASSIGNED_213: return "UNASSIGNED_213"; break; case ip_protocol_t::UNASSIGNED_214: return "UNASSIGNED_214"; break; case ip_protocol_t::UNASSIGNED_215: return "UNASSIGNED_215"; break; case ip_protocol_t::UNASSIGNED_216: return "UNASSIGNED_216"; break; case ip_protocol_t::UNASSIGNED_217: return "UNASSIGNED_217"; break; case ip_protocol_t::UNASSIGNED_218: return "UNASSIGNED_218"; break; case ip_protocol_t::UNASSIGNED_219: return "UNASSIGNED_219"; break; case ip_protocol_t::UNASSIGNED_220: return "UNASSIGNED_220"; break; case ip_protocol_t::UNASSIGNED_221: return "UNASSIGNED_221"; break; case ip_protocol_t::UNASSIGNED_222: return "UNASSIGNED_222"; break; case ip_protocol_t::UNASSIGNED_223: return "UNASSIGNED_223"; break; case ip_protocol_t::UNASSIGNED_224: return "UNASSIGNED_224"; break; case ip_protocol_t::UNASSIGNED_225: return "UNASSIGNED_225"; break; case ip_protocol_t::UNASSIGNED_226: return "UNASSIGNED_226"; break; case ip_protocol_t::UNASSIGNED_227: return "UNASSIGNED_227"; break; case ip_protocol_t::UNASSIGNED_228: return "UNASSIGNED_228"; break; case ip_protocol_t::UNASSIGNED_229: return "UNASSIGNED_229"; break; case ip_protocol_t::UNASSIGNED_230: return "UNASSIGNED_230"; break; case ip_protocol_t::UNASSIGNED_231: return "UNASSIGNED_231"; break; case ip_protocol_t::UNASSIGNED_232: return "UNASSIGNED_232"; break; case ip_protocol_t::UNASSIGNED_233: return "UNASSIGNED_233"; break; case ip_protocol_t::UNASSIGNED_234: return "UNASSIGNED_234"; break; case ip_protocol_t::UNASSIGNED_235: return "UNASSIGNED_235"; break; case ip_protocol_t::UNASSIGNED_236: return "UNASSIGNED_236"; break; case ip_protocol_t::UNASSIGNED_237: return "UNASSIGNED_237"; break; case ip_protocol_t::UNASSIGNED_238: return "UNASSIGNED_238"; break; case ip_protocol_t::UNASSIGNED_239: return "UNASSIGNED_239"; break; case ip_protocol_t::UNASSIGNED_240: return "UNASSIGNED_240"; break; case ip_protocol_t::UNASSIGNED_241: return "UNASSIGNED_241"; break; case ip_protocol_t::UNASSIGNED_242: return "UNASSIGNED_242"; break; case ip_protocol_t::UNASSIGNED_243: return "UNASSIGNED_243"; break; case ip_protocol_t::UNASSIGNED_244: return "UNASSIGNED_244"; break; case ip_protocol_t::UNASSIGNED_245: return "UNASSIGNED_245"; break; case ip_protocol_t::UNASSIGNED_246: return "UNASSIGNED_246"; break; case ip_protocol_t::UNASSIGNED_247: return "UNASSIGNED_247"; break; case ip_protocol_t::UNASSIGNED_248: return "UNASSIGNED_248"; break; case ip_protocol_t::UNASSIGNED_249: return "UNASSIGNED_249"; break; case ip_protocol_t::UNASSIGNED_250: return "UNASSIGNED_250"; break; case ip_protocol_t::UNASSIGNED_251: return "UNASSIGNED_251"; break; case ip_protocol_t::UNASSIGNED_252: return "UNASSIGNED_252"; break; case ip_protocol_t::UNKNOWN_253: return "UNKNOWN_253"; break; case ip_protocol_t::UNKNOWN_254: return "UNKNOWN_254"; break; case ip_protocol_t::RESERVED: return "RESERVED"; break; } } bool read_protocol_from_string(const std::string& protocol_string, ip_protocol_t& ip_protocol_enum) { std::string protocol_string_lower = boost::algorithm::to_lower_copy(protocol_string); if (protocol_string_lower == "") { return false; } else if (protocol_string_lower == "hopopt") { ip_protocol_enum = ip_protocol_t::HOPOPT; return true; } else if (protocol_string_lower == "icmp") { ip_protocol_enum = ip_protocol_t::ICMP; return true; } else if (protocol_string_lower == "igmp") { ip_protocol_enum = ip_protocol_t::IGMP; return true; } else if (protocol_string_lower == "ggp") { ip_protocol_enum = ip_protocol_t::GGP; return true; } else if (protocol_string_lower == "ipv4") { ip_protocol_enum = ip_protocol_t::IPV4; return true; } else if (protocol_string_lower == "st") { ip_protocol_enum = ip_protocol_t::ST; return true; } else if (protocol_string_lower == "tcp") { ip_protocol_enum = ip_protocol_t::TCP; return true; } else if (protocol_string_lower == "cbt") { ip_protocol_enum = ip_protocol_t::CBT; return true; } else if (protocol_string_lower == "egp") { ip_protocol_enum = ip_protocol_t::EGP; return true; } else if (protocol_string_lower == "igp") { ip_protocol_enum = ip_protocol_t::IGP; return true; } else if (protocol_string_lower == "bbn_rcc_mon") { ip_protocol_enum = ip_protocol_t::BBN_RCC_MON; return true; } else if (protocol_string_lower == "nvp_ii") { ip_protocol_enum = ip_protocol_t::NVP_II; return true; } else if (protocol_string_lower == "pup") { ip_protocol_enum = ip_protocol_t::PUP; return true; } else if (protocol_string_lower == "argus_deprecated") { ip_protocol_enum = ip_protocol_t::ARGUS_DEPRECATED; return true; } else if (protocol_string_lower == "emcon") { ip_protocol_enum = ip_protocol_t::EMCON; return true; } else if (protocol_string_lower == "xnet") { ip_protocol_enum = ip_protocol_t::XNET; return true; } else if (protocol_string_lower == "chaos") { ip_protocol_enum = ip_protocol_t::CHAOS; return true; } else if (protocol_string_lower == "udp") { ip_protocol_enum = ip_protocol_t::UDP; return true; } else if (protocol_string_lower == "mux") { ip_protocol_enum = ip_protocol_t::MUX; return true; } else if (protocol_string_lower == "dcn_meas") { ip_protocol_enum = ip_protocol_t::DCN_MEAS; return true; } else if (protocol_string_lower == "hmp") { ip_protocol_enum = ip_protocol_t::HMP; return true; } else if (protocol_string_lower == "prm") { ip_protocol_enum = ip_protocol_t::PRM; return true; } else if (protocol_string_lower == "xns_idp") { ip_protocol_enum = ip_protocol_t::XNS_IDP; return true; } else if (protocol_string_lower == "trunk_1") { ip_protocol_enum = ip_protocol_t::TRUNK_1; return true; } else if (protocol_string_lower == "trunk_2") { ip_protocol_enum = ip_protocol_t::TRUNK_2; return true; } else if (protocol_string_lower == "leaf_1") { ip_protocol_enum = ip_protocol_t::LEAF_1; return true; } else if (protocol_string_lower == "leaf_2") { ip_protocol_enum = ip_protocol_t::LEAF_2; return true; } else if (protocol_string_lower == "rdp") { ip_protocol_enum = ip_protocol_t::RDP; return true; } else if (protocol_string_lower == "irtp") { ip_protocol_enum = ip_protocol_t::IRTP; return true; } else if (protocol_string_lower == "iso_tp4") { ip_protocol_enum = ip_protocol_t::ISO_TP4; return true; } else if (protocol_string_lower == "netblt") { ip_protocol_enum = ip_protocol_t::NETBLT; return true; } else if (protocol_string_lower == "mfe_nsp") { ip_protocol_enum = ip_protocol_t::MFE_NSP; return true; } else if (protocol_string_lower == "merit_inp") { ip_protocol_enum = ip_protocol_t::MERIT_INP; return true; } else if (protocol_string_lower == "dccp") { ip_protocol_enum = ip_protocol_t::DCCP; return true; } else if (protocol_string_lower == "threepc") { ip_protocol_enum = ip_protocol_t::THREEPC; return true; } else if (protocol_string_lower == "idpr") { ip_protocol_enum = ip_protocol_t::IDPR; return true; } else if (protocol_string_lower == "xtp") { ip_protocol_enum = ip_protocol_t::XTP; return true; } else if (protocol_string_lower == "ddp") { ip_protocol_enum = ip_protocol_t::DDP; return true; } else if (protocol_string_lower == "idpr_cmtp") { ip_protocol_enum = ip_protocol_t::IDPR_CMTP; return true; } else if (protocol_string_lower == "tppppp") { ip_protocol_enum = ip_protocol_t::TPPPPP; return true; } else if (protocol_string_lower == "il") { ip_protocol_enum = ip_protocol_t::IL; return true; } else if (protocol_string_lower == "ipv6") { ip_protocol_enum = ip_protocol_t::IPV6; return true; } else if (protocol_string_lower == "sdrp") { ip_protocol_enum = ip_protocol_t::SDRP; return true; } else if (protocol_string_lower == "ipv6_route") { ip_protocol_enum = ip_protocol_t::IPV6_ROUTE; return true; } else if (protocol_string_lower == "ipv6_frag") { ip_protocol_enum = ip_protocol_t::IPV6_FRAG; return true; } else if (protocol_string_lower == "idrp") { ip_protocol_enum = ip_protocol_t::IDRP; return true; } else if (protocol_string_lower == "rsvp") { ip_protocol_enum = ip_protocol_t::RSVP; return true; } else if (protocol_string_lower == "gre") { ip_protocol_enum = ip_protocol_t::GRE; return true; } else if (protocol_string_lower == "dsr") { ip_protocol_enum = ip_protocol_t::DSR; return true; } else if (protocol_string_lower == "bna") { ip_protocol_enum = ip_protocol_t::BNA; return true; } else if (protocol_string_lower == "esp") { ip_protocol_enum = ip_protocol_t::ESP; return true; } else if (protocol_string_lower == "ah") { ip_protocol_enum = ip_protocol_t::AH; return true; } else if (protocol_string_lower == "i_nlsp") { ip_protocol_enum = ip_protocol_t::I_NLSP; return true; } else if (protocol_string_lower == "swipe_deprecated") { ip_protocol_enum = ip_protocol_t::SWIPE_DEPRECATED; return true; } else if (protocol_string_lower == "narp") { ip_protocol_enum = ip_protocol_t::NARP; return true; } else if (protocol_string_lower == "mobile") { ip_protocol_enum = ip_protocol_t::MOBILE; return true; } else if (protocol_string_lower == "tlsp") { ip_protocol_enum = ip_protocol_t::TLSP; return true; } else if (protocol_string_lower == "skip") { ip_protocol_enum = ip_protocol_t::SKIP; return true; } else if (protocol_string_lower == "ipv6_icmp") { ip_protocol_enum = ip_protocol_t::IPV6_ICMP; return true; } else if (protocol_string_lower == "ipv6_nonxt") { ip_protocol_enum = ip_protocol_t::IPV6_NONXT; return true; } else if (protocol_string_lower == "ipv6_opts") { ip_protocol_enum = ip_protocol_t::IPV6_OPTS; return true; } else if (protocol_string_lower == "unknown_61") { ip_protocol_enum = ip_protocol_t::UNKNOWN_61; return true; } else if (protocol_string_lower == "cftp") { ip_protocol_enum = ip_protocol_t::CFTP; return true; } else if (protocol_string_lower == "unknown_63") { ip_protocol_enum = ip_protocol_t::UNKNOWN_63; return true; } else if (protocol_string_lower == "sat_expak") { ip_protocol_enum = ip_protocol_t::SAT_EXPAK; return true; } else if (protocol_string_lower == "kryptolan") { ip_protocol_enum = ip_protocol_t::KRYPTOLAN; return true; } else if (protocol_string_lower == "rvd") { ip_protocol_enum = ip_protocol_t::RVD; return true; } else if (protocol_string_lower == "ippc") { ip_protocol_enum = ip_protocol_t::IPPC; return true; } else if (protocol_string_lower == "unknown_68") { ip_protocol_enum = ip_protocol_t::UNKNOWN_68; return true; } else if (protocol_string_lower == "sat_mon") { ip_protocol_enum = ip_protocol_t::SAT_MON; return true; } else if (protocol_string_lower == "visa") { ip_protocol_enum = ip_protocol_t::VISA; return true; } else if (protocol_string_lower == "ipcv") { ip_protocol_enum = ip_protocol_t::IPCV; return true; } else if (protocol_string_lower == "cpnx") { ip_protocol_enum = ip_protocol_t::CPNX; return true; } else if (protocol_string_lower == "cphb") { ip_protocol_enum = ip_protocol_t::CPHB; return true; } else if (protocol_string_lower == "wsn") { ip_protocol_enum = ip_protocol_t::WSN; return true; } else if (protocol_string_lower == "pvp") { ip_protocol_enum = ip_protocol_t::PVP; return true; } else if (protocol_string_lower == "br_sat_mon") { ip_protocol_enum = ip_protocol_t::BR_SAT_MON; return true; } else if (protocol_string_lower == "sun_nd") { ip_protocol_enum = ip_protocol_t::SUN_ND; return true; } else if (protocol_string_lower == "wb_mon") { ip_protocol_enum = ip_protocol_t::WB_MON; return true; } else if (protocol_string_lower == "wb_expak") { ip_protocol_enum = ip_protocol_t::WB_EXPAK; return true; } else if (protocol_string_lower == "iso_ip") { ip_protocol_enum = ip_protocol_t::ISO_IP; return true; } else if (protocol_string_lower == "vmtp") { ip_protocol_enum = ip_protocol_t::VMTP; return true; } else if (protocol_string_lower == "secure_vmtp") { ip_protocol_enum = ip_protocol_t::SECURE_VMTP; return true; } else if (protocol_string_lower == "vines") { ip_protocol_enum = ip_protocol_t::VINES; return true; } else if (protocol_string_lower == "iptm_or_ttp") { ip_protocol_enum = ip_protocol_t::IPTM_OR_TTP; return true; } else if (protocol_string_lower == "nsfnet_igp") { ip_protocol_enum = ip_protocol_t::NSFNET_IGP; return true; } else if (protocol_string_lower == "dgp") { ip_protocol_enum = ip_protocol_t::DGP; return true; } else if (protocol_string_lower == "tcf") { ip_protocol_enum = ip_protocol_t::TCF; return true; } else if (protocol_string_lower == "eigrp") { ip_protocol_enum = ip_protocol_t::EIGRP; return true; } else if (protocol_string_lower == "ospfigp") { ip_protocol_enum = ip_protocol_t::OSPFIGP; return true; } else if (protocol_string_lower == "sprite_rpc") { ip_protocol_enum = ip_protocol_t::SPRITE_RPC; return true; } else if (protocol_string_lower == "larp") { ip_protocol_enum = ip_protocol_t::LARP; return true; } else if (protocol_string_lower == "mtp") { ip_protocol_enum = ip_protocol_t::MTP; return true; } else if (protocol_string_lower == "ax_25") { ip_protocol_enum = ip_protocol_t::AX_25; return true; } else if (protocol_string_lower == "ipip") { ip_protocol_enum = ip_protocol_t::IPIP; return true; } else if (protocol_string_lower == "micp_deprecated") { ip_protocol_enum = ip_protocol_t::MICP_DEPRECATED; return true; } else if (protocol_string_lower == "scc_sp") { ip_protocol_enum = ip_protocol_t::SCC_SP; return true; } else if (protocol_string_lower == "etherip") { ip_protocol_enum = ip_protocol_t::ETHERIP; return true; } else if (protocol_string_lower == "encap") { ip_protocol_enum = ip_protocol_t::ENCAP; return true; } else if (protocol_string_lower == "unknown_99") { ip_protocol_enum = ip_protocol_t::UNKNOWN_99; return true; } else if (protocol_string_lower == "gmtp") { ip_protocol_enum = ip_protocol_t::GMTP; return true; } else if (protocol_string_lower == "ifmp") { ip_protocol_enum = ip_protocol_t::IFMP; return true; } else if (protocol_string_lower == "pnni") { ip_protocol_enum = ip_protocol_t::PNNI; return true; } else if (protocol_string_lower == "pim") { ip_protocol_enum = ip_protocol_t::PIM; return true; } else if (protocol_string_lower == "aris") { ip_protocol_enum = ip_protocol_t::ARIS; return true; } else if (protocol_string_lower == "scps") { ip_protocol_enum = ip_protocol_t::SCPS; return true; } else if (protocol_string_lower == "qnx") { ip_protocol_enum = ip_protocol_t::QNX; return true; } else if (protocol_string_lower == "a_n") { ip_protocol_enum = ip_protocol_t::A_N; return true; } else if (protocol_string_lower == "ipcomp") { ip_protocol_enum = ip_protocol_t::IPCOMP; return true; } else if (protocol_string_lower == "snp") { ip_protocol_enum = ip_protocol_t::SNP; return true; } else if (protocol_string_lower == "compaq_peer") { ip_protocol_enum = ip_protocol_t::COMPAQ_PEER; return true; } else if (protocol_string_lower == "ipx_in_ip") { ip_protocol_enum = ip_protocol_t::IPX_IN_IP; return true; } else if (protocol_string_lower == "vrrp") { ip_protocol_enum = ip_protocol_t::VRRP; return true; } else if (protocol_string_lower == "pgm") { ip_protocol_enum = ip_protocol_t::PGM; return true; } else if (protocol_string_lower == "unknown_114") { ip_protocol_enum = ip_protocol_t::UNKNOWN_114; return true; } else if (protocol_string_lower == "l2tp") { ip_protocol_enum = ip_protocol_t::L2TP; return true; } else if (protocol_string_lower == "ddx") { ip_protocol_enum = ip_protocol_t::DDX; return true; } else if (protocol_string_lower == "iatp") { ip_protocol_enum = ip_protocol_t::IATP; return true; } else if (protocol_string_lower == "stp") { ip_protocol_enum = ip_protocol_t::STP; return true; } else if (protocol_string_lower == "srp") { ip_protocol_enum = ip_protocol_t::SRP; return true; } else if (protocol_string_lower == "uti") { ip_protocol_enum = ip_protocol_t::UTI; return true; } else if (protocol_string_lower == "smp") { ip_protocol_enum = ip_protocol_t::SMP; return true; } else if (protocol_string_lower == "sm_deprecated") { ip_protocol_enum = ip_protocol_t::SM_DEPRECATED; return true; } else if (protocol_string_lower == "ptp") { ip_protocol_enum = ip_protocol_t::PTP; return true; } else if (protocol_string_lower == "isisoveripv4") { ip_protocol_enum = ip_protocol_t::ISISOVERIPV4; return true; } else if (protocol_string_lower == "fire") { ip_protocol_enum = ip_protocol_t::FIRE; return true; } else if (protocol_string_lower == "crtp") { ip_protocol_enum = ip_protocol_t::CRTP; return true; } else if (protocol_string_lower == "crudp") { ip_protocol_enum = ip_protocol_t::CRUDP; return true; } else if (protocol_string_lower == "sscopmce") { ip_protocol_enum = ip_protocol_t::SSCOPMCE; return true; } else if (protocol_string_lower == "iplt") { ip_protocol_enum = ip_protocol_t::IPLT; return true; } else if (protocol_string_lower == "sps") { ip_protocol_enum = ip_protocol_t::SPS; return true; } else if (protocol_string_lower == "pipe") { ip_protocol_enum = ip_protocol_t::PIPE; return true; } else if (protocol_string_lower == "sctp") { ip_protocol_enum = ip_protocol_t::SCTP; return true; } else if (protocol_string_lower == "fc") { ip_protocol_enum = ip_protocol_t::FC; return true; } else if (protocol_string_lower == "rsvp_e2e_ignore") { ip_protocol_enum = ip_protocol_t::RSVP_E2E_IGNORE; return true; } else if (protocol_string_lower == "mobilityheader") { ip_protocol_enum = ip_protocol_t::MOBILITYHEADER; return true; } else if (protocol_string_lower == "udplite") { ip_protocol_enum = ip_protocol_t::UDPLITE; return true; } else if (protocol_string_lower == "mpls_in_ip") { ip_protocol_enum = ip_protocol_t::MPLS_IN_IP; return true; } else if (protocol_string_lower == "manet") { ip_protocol_enum = ip_protocol_t::MANET; return true; } else if (protocol_string_lower == "hip") { ip_protocol_enum = ip_protocol_t::HIP; return true; } else if (protocol_string_lower == "shim6") { ip_protocol_enum = ip_protocol_t::SHIM6; return true; } else if (protocol_string_lower == "wesp") { ip_protocol_enum = ip_protocol_t::WESP; return true; } else if (protocol_string_lower == "rohc") { ip_protocol_enum = ip_protocol_t::ROHC; return true; } else if (protocol_string_lower == "ethernet") { ip_protocol_enum = ip_protocol_t::ETHERNET; return true; } else if (protocol_string_lower == "unassigned_144") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_144; return true; } else if (protocol_string_lower == "unassigned_145") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_145; return true; } else if (protocol_string_lower == "unassigned_146") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_146; return true; } else if (protocol_string_lower == "unassigned_147") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_147; return true; } else if (protocol_string_lower == "unassigned_148") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_148; return true; } else if (protocol_string_lower == "unassigned_149") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_149; return true; } else if (protocol_string_lower == "unassigned_150") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_150; return true; } else if (protocol_string_lower == "unassigned_151") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_151; return true; } else if (protocol_string_lower == "unassigned_152") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_152; return true; } else if (protocol_string_lower == "unassigned_153") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_153; return true; } else if (protocol_string_lower == "unassigned_154") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_154; return true; } else if (protocol_string_lower == "unassigned_155") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_155; return true; } else if (protocol_string_lower == "unassigned_156") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_156; return true; } else if (protocol_string_lower == "unassigned_157") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_157; return true; } else if (protocol_string_lower == "unassigned_158") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_158; return true; } else if (protocol_string_lower == "unassigned_159") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_159; return true; } else if (protocol_string_lower == "unassigned_160") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_160; return true; } else if (protocol_string_lower == "unassigned_161") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_161; return true; } else if (protocol_string_lower == "unassigned_162") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_162; return true; } else if (protocol_string_lower == "unassigned_163") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_163; return true; } else if (protocol_string_lower == "unassigned_164") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_164; return true; } else if (protocol_string_lower == "unassigned_165") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_165; return true; } else if (protocol_string_lower == "unassigned_166") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_166; return true; } else if (protocol_string_lower == "unassigned_167") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_167; return true; } else if (protocol_string_lower == "unassigned_168") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_168; return true; } else if (protocol_string_lower == "unassigned_169") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_169; return true; } else if (protocol_string_lower == "unassigned_170") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_170; return true; } else if (protocol_string_lower == "unassigned_171") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_171; return true; } else if (protocol_string_lower == "unassigned_172") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_172; return true; } else if (protocol_string_lower == "unassigned_173") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_173; return true; } else if (protocol_string_lower == "unassigned_174") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_174; return true; } else if (protocol_string_lower == "unassigned_175") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_175; return true; } else if (protocol_string_lower == "unassigned_176") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_176; return true; } else if (protocol_string_lower == "unassigned_177") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_177; return true; } else if (protocol_string_lower == "unassigned_178") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_178; return true; } else if (protocol_string_lower == "unassigned_179") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_179; return true; } else if (protocol_string_lower == "unassigned_180") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_180; return true; } else if (protocol_string_lower == "unassigned_181") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_181; return true; } else if (protocol_string_lower == "unassigned_182") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_182; return true; } else if (protocol_string_lower == "unassigned_183") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_183; return true; } else if (protocol_string_lower == "unassigned_184") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_184; return true; } else if (protocol_string_lower == "unassigned_185") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_185; return true; } else if (protocol_string_lower == "unassigned_186") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_186; return true; } else if (protocol_string_lower == "unassigned_187") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_187; return true; } else if (protocol_string_lower == "unassigned_188") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_188; return true; } else if (protocol_string_lower == "unassigned_189") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_189; return true; } else if (protocol_string_lower == "unassigned_190") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_190; return true; } else if (protocol_string_lower == "unassigned_191") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_191; return true; } else if (protocol_string_lower == "unassigned_192") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_192; return true; } else if (protocol_string_lower == "unassigned_193") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_193; return true; } else if (protocol_string_lower == "unassigned_194") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_194; return true; } else if (protocol_string_lower == "unassigned_195") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_195; return true; } else if (protocol_string_lower == "unassigned_196") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_196; return true; } else if (protocol_string_lower == "unassigned_197") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_197; return true; } else if (protocol_string_lower == "unassigned_198") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_198; return true; } else if (protocol_string_lower == "unassigned_199") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_199; return true; } else if (protocol_string_lower == "unassigned_200") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_200; return true; } else if (protocol_string_lower == "unassigned_201") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_201; return true; } else if (protocol_string_lower == "unassigned_202") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_202; return true; } else if (protocol_string_lower == "unassigned_203") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_203; return true; } else if (protocol_string_lower == "unassigned_204") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_204; return true; } else if (protocol_string_lower == "unassigned_205") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_205; return true; } else if (protocol_string_lower == "unassigned_206") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_206; return true; } else if (protocol_string_lower == "unassigned_207") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_207; return true; } else if (protocol_string_lower == "unassigned_208") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_208; return true; } else if (protocol_string_lower == "unassigned_209") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_209; return true; } else if (protocol_string_lower == "unassigned_210") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_210; return true; } else if (protocol_string_lower == "unassigned_211") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_211; return true; } else if (protocol_string_lower == "unassigned_212") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_212; return true; } else if (protocol_string_lower == "unassigned_213") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_213; return true; } else if (protocol_string_lower == "unassigned_214") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_214; return true; } else if (protocol_string_lower == "unassigned_215") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_215; return true; } else if (protocol_string_lower == "unassigned_216") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_216; return true; } else if (protocol_string_lower == "unassigned_217") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_217; return true; } else if (protocol_string_lower == "unassigned_218") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_218; return true; } else if (protocol_string_lower == "unassigned_219") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_219; return true; } else if (protocol_string_lower == "unassigned_220") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_220; return true; } else if (protocol_string_lower == "unassigned_221") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_221; return true; } else if (protocol_string_lower == "unassigned_222") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_222; return true; } else if (protocol_string_lower == "unassigned_223") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_223; return true; } else if (protocol_string_lower == "unassigned_224") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_224; return true; } else if (protocol_string_lower == "unassigned_225") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_225; return true; } else if (protocol_string_lower == "unassigned_226") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_226; return true; } else if (protocol_string_lower == "unassigned_227") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_227; return true; } else if (protocol_string_lower == "unassigned_228") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_228; return true; } else if (protocol_string_lower == "unassigned_229") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_229; return true; } else if (protocol_string_lower == "unassigned_230") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_230; return true; } else if (protocol_string_lower == "unassigned_231") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_231; return true; } else if (protocol_string_lower == "unassigned_232") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_232; return true; } else if (protocol_string_lower == "unassigned_233") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_233; return true; } else if (protocol_string_lower == "unassigned_234") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_234; return true; } else if (protocol_string_lower == "unassigned_235") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_235; return true; } else if (protocol_string_lower == "unassigned_236") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_236; return true; } else if (protocol_string_lower == "unassigned_237") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_237; return true; } else if (protocol_string_lower == "unassigned_238") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_238; return true; } else if (protocol_string_lower == "unassigned_239") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_239; return true; } else if (protocol_string_lower == "unassigned_240") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_240; return true; } else if (protocol_string_lower == "unassigned_241") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_241; return true; } else if (protocol_string_lower == "unassigned_242") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_242; return true; } else if (protocol_string_lower == "unassigned_243") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_243; return true; } else if (protocol_string_lower == "unassigned_244") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_244; return true; } else if (protocol_string_lower == "unassigned_245") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_245; return true; } else if (protocol_string_lower == "unassigned_246") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_246; return true; } else if (protocol_string_lower == "unassigned_247") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_247; return true; } else if (protocol_string_lower == "unassigned_248") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_248; return true; } else if (protocol_string_lower == "unassigned_249") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_249; return true; } else if (protocol_string_lower == "unassigned_250") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_250; return true; } else if (protocol_string_lower == "unassigned_251") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_251; return true; } else if (protocol_string_lower == "unassigned_252") { ip_protocol_enum = ip_protocol_t::UNASSIGNED_252; return true; } else if (protocol_string_lower == "unknown_253") { ip_protocol_enum = ip_protocol_t::UNKNOWN_253; return true; } else if (protocol_string_lower == "unknown_254") { ip_protocol_enum = ip_protocol_t::UNKNOWN_254; return true; } else if (protocol_string_lower == "reserved") { ip_protocol_enum = ip_protocol_t::RESERVED; return true; } else { return false; } } ip_protocol_t get_ip_protocol_enum_type_from_integer(uint8_t protocol_as_integer) { return static_cast(protocol_as_integer); } uint8_t get_ip_protocol_enum_as_number(ip_protocol_t ip_protocol_enum) { return static_cast::type>(ip_protocol_enum); } pavel-odintsov-fastnetmon-394fbe0/src/ip_lookup_tree.hpp000066400000000000000000000115651520703010000235720ustar00rootroot00000000000000#pragma once #include #include "fastnetmon_networks.hpp" #include "libpatricia/patricia.hpp" // Here we have pretty nice wrappers for patricia tree class lookup_tree_128bit_t { public: lookup_tree_128bit_t() { patricia_tree = New_Patricia(128); } ~lookup_tree_128bit_t() { if (patricia_tree) { Destroy_Patricia(patricia_tree); patricia_tree = nullptr; } } bool add_subnet(const subnet_ipv6_cidr_mask_t& subnet) { // TODO: rewrite this code to native prefix adding. Get rid useless string conversion std::string subnet_as_string = convert_ipv6_subnet_to_string(subnet); make_and_lookup_ipv6(patricia_tree, (char*)subnet_as_string.c_str()); return true; } // Lookup this IP in Patricia tree bool lookup_ip(const in6_addr& client_ipv6_address) const { 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; patricia_node_t* found_patrica_node = patricia_search_best2(patricia_tree, &prefix_for_check_address, 1); // Could not find anything if (found_patrica_node == NULL) { return false; } return true; } // TODO: rework get_packet_direction_ipv6 and make it public public: patricia_tree_t* patricia_tree = nullptr; }; class lookup_tree_32bit_t { public: lookup_tree_32bit_t() { patricia_tree = New_Patricia(32); } bool add_subnet(const subnet_cidr_mask_t& subnet) { // TODO: rewrite this code to native prefix adding. Get rid useless string conversion std::string subnet_as_string = convert_ipv4_subnet_to_string(subnet); make_and_lookup(patricia_tree, (char*)subnet_as_string.c_str()); return true; } // Lookup this IP in Patricia tree bool lookup_ip(uint32_t ip_address_big_endian) const { prefix_t prefix_for_check_address; prefix_for_check_address.add.sin.s_addr = ip_address_big_endian; 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); // Could not find anything if (found_patrica_node == NULL) { return false; } return true; } // Lookup this network in Patricia tree bool lookup_network(const subnet_cidr_mask_t& subnet) const { 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); // Could not find anything if (found_patrica_node == NULL) { return false; } return true; } // Lookups this IP in Patricia tree and returns network prefix which consists this IP in our tree // Returns false when IP is not a part of tree bool lookup_network_which_includes_ip(uint32_t ip_address_big_endian, subnet_cidr_mask_t& subnet) const { prefix_t prefix_for_check_address; prefix_for_check_address.add.sin.s_addr = ip_address_big_endian; 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); // Could not find anything if (found_patrica_node == NULL) { return false; } prefix_t* prefix = found_patrica_node->prefix; // It should not happen but I prefer to be on safe side if (prefix == NULL) { return false; } subnet.subnet_address = prefix->add.sin.s_addr; subnet.cidr_prefix_length = prefix->bitlen; return true; } ~lookup_tree_32bit_t() { if (patricia_tree) { Destroy_Patricia(patricia_tree); patricia_tree = nullptr; } } // Allow access to private variables from tests friend class patricia_process_ipv4_Test; friend class patricia_positive_lookup_ipv4_check_data_field_value_Test; friend class patricia_positive_lookup_ipv4_lookup_24_in_same_24_Test; friend class patricia_positive_lookup_ipv4_Test; friend class patricia_positive_lookup_ipv4_lookup_24_in_same_24_not_inclusive_Test; friend class patricia_positive_lookup_32_in32_with_24_Test; friend class patricia_positive_lookup_multiple_networks_Test; friend class patricia_positive_lookup_32_in32_Test; private: patricia_tree_t* patricia_tree = nullptr; }; pavel-odintsov-fastnetmon-394fbe0/src/ip_lookup_tree_with_payload.hpp000066400000000000000000000147341520703010000263370ustar00rootroot00000000000000#pragma once #include "fastnetmon_networks.hpp" #include "libpatricia/patricia.hpp" #include // This is safe wrapper for Patricia with support for storing data directly in tree leafs without doing memory // allocations We can store only types which does not exceed void* by length template class lookup_tree_128bit_with_payload_t { public: lookup_tree_128bit_with_payload_t() { patricia_tree = New_Patricia(128); } // Loads all the elements from passed tree to our tree // using inline storage method void load_inline(const lookup_tree_128bit_with_payload_t& another_tree) { patricia_process(another_tree.patricia_tree, [this](prefix_t* prefix, void* data) { subnet_ipv6_cidr_mask_t subnet; memcpy(&subnet.subnet_address, &prefix->add.sin6, sizeof(subnet.subnet_address)); subnet.cidr_prefix_length = prefix->bitlen; // Add it to tree this->add_subnet_with_payload_inline(subnet, (T)data); }); } bool add_subnet_with_payload_inline(const subnet_ipv6_cidr_mask_t& ipv6_subnet, T object_to_store) { std::string subnet_as_string = convert_ipv6_subnet_to_string(ipv6_subnet); make_and_lookup_ipv6_with_data(patricia_tree, (char*)subnet_as_string.c_str(), (void*)object_to_store); return true; } // Try to find payload for certain subnet. But we return value directly bool lookup_value_inline_for_subnet(const subnet_ipv6_cidr_mask_t& ipv6_subnet, T& target_object_ptr) { prefix_t prefix_for_check_address; prefix_for_check_address.add.sin6 = ipv6_subnet.subnet_address; prefix_for_check_address.family = AF_INET6; prefix_for_check_address.bitlen = ipv6_subnet.cidr_prefix_length; patricia_node_t* found_patrica_node = patricia_search_best2(patricia_tree, &prefix_for_check_address, 1); // Could not find anything if (found_patrica_node == NULL) { return false; } // Return value itself target_object_ptr = (T)found_patrica_node->data; return true; } // Try to find payload for certain subnet. But we return value directly // It will return data only if we have exactly this subnet with exactly this prefix bool lookup_value_inline_for_subnet_exact_match(const subnet_ipv6_cidr_mask_t& ipv6_subnet, T& target_object_ptr) { prefix_t prefix_for_check_address; prefix_for_check_address.add.sin6 = ipv6_subnet.subnet_address; prefix_for_check_address.family = AF_INET6; prefix_for_check_address.bitlen = ipv6_subnet.cidr_prefix_length; patricia_node_t* found_patrica_node = patricia_search_exact(patricia_tree, &prefix_for_check_address); // Could not find anything if (found_patrica_node == NULL) { return false; } // Return value itself target_object_ptr = (T)found_patrica_node->data; return true; } ~lookup_tree_128bit_with_payload_t() { if (patricia_tree) { Destroy_Patricia(patricia_tree); } } patricia_tree_t* patricia_tree = nullptr; }; // This is safe wrapper for Patricia with support for storing data directly in tree leafs without doing memory // allocations We can store only types which does not exceed void* by length template class lookup_tree_32bit_with_payload_t { public: lookup_tree_32bit_with_payload_t() { patricia_tree = New_Patricia(32); } // Loads all the elements from passed tree to our tree // using inline storage method void load_inline(const lookup_tree_32bit_with_payload_t& another_tree) { patricia_process(another_tree.patricia_tree, [this](prefix_t* prefix, void* data) { // Construct our network from low level structure in Patricia subnet_cidr_mask_t subnet; subnet.subnet_address = prefix->add.sin.s_addr; subnet.cidr_prefix_length = prefix->bitlen; // Add it to tree add_subnet_with_payload_inline(subnet, (T)data); }); } bool add_subnet_with_payload_inline(const subnet_cidr_mask_t& subnet, T object_to_store) { std::string subnet_as_string = convert_ipv4_subnet_to_string(subnet); make_and_lookup_with_data(patricia_tree, (char*)subnet_as_string.c_str(), (void*)object_to_store); return true; } // Try to find payload for certain subnet. But we return value directly bool lookup_value_inline_for_subnet(const subnet_cidr_mask_t& subnet, T& target_object_ptr) { prefix_t prefix_for_check_address; prefix_for_check_address.add.sin.s_addr = subnet.subnet_address; prefix_for_check_address.family = AF_INET; prefix_for_check_address.bitlen = subnet.cidr_prefix_length; patricia_node_t* found_patrica_node = patricia_search_best2(patricia_tree, &prefix_for_check_address, 1); // Could not find anything if (found_patrica_node == NULL) { return false; } // Return value itself target_object_ptr = (T)found_patrica_node->data; return true; } // Try to find payload for certain subnet. But we return value directly // It will return data only if we have exactly this subnet with exactly t bool lookup_value_inline_for_subnet_exact_match(const subnet_cidr_mask_t& subnet, T& target_object_ptr) { prefix_t prefix_for_check_address; prefix_for_check_address.add.sin.s_addr = subnet.subnet_address; prefix_for_check_address.family = AF_INET; prefix_for_check_address.bitlen = subnet.cidr_prefix_length; patricia_node_t* found_patrica_node = patricia_search_exact(patricia_tree, &prefix_for_check_address); // Could not find anything if (found_patrica_node == NULL) { return false; } // Return value itself target_object_ptr = (T)found_patrica_node->data; return true; } // Try to find payload for certain IP. But we return pointer to value instead of value bool lookup_value_inline_for_ip(uint32_t ip, T& target_object_ptr) { subnet_cidr_mask_t subnet(ip, 32); return lookup_value_inline_for_subnet(subnet, target_object_ptr); } ~lookup_tree_32bit_with_payload_t() { if (patricia_tree) { Destroy_Patricia(patricia_tree); patricia_tree = nullptr; } } patricia_tree_t* patricia_tree = nullptr; }; pavel-odintsov-fastnetmon-394fbe0/src/ipfix_fields/000077500000000000000000000000001520703010000224765ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/ipfix_fields/ipfix_fields.csv000066400000000000000000005644751520703010000257050ustar00rootroot00000000000000ElementID,Name,Abstract Data Type,Data Type Semantics,Status,Description,Units,Range,Additional Information,Reference,Revision,Date 0,Reserved,,,,,,,,[RFC5102],,2013-02-18 1,octetDeltaCount,unsigned64,deltaCounter,current,"The number of octets since the previous report (if any) in incoming packets for this Flow at the Observation Point. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 2,packetDeltaCount,unsigned64,deltaCounter,current,"The number of incoming packets since the previous report (if any) for this Flow at the Observation Point.",packets,,,[RFC5102],0,2013-02-18 3,deltaFlowCount,unsigned64,deltaCounter,current,"The conservative count of Original Flows contributing to this Aggregated Flow; may be distributed via any of the methods expressed by the valueDistributionMethod Information Element.",flows,,,[RFC7015],1,2013-06-25 4,protocolIdentifier,unsigned8,identifier,current,"The value of the protocol number in the IP packet header. The protocol number identifies the IP packet payload type. Protocol numbers are defined in the IANA Protocol Numbers registry. In Internet Protocol version 4 (IPv4), this is carried in the Protocol field. In Internet Protocol version 6 (IPv6), this is carried in the Next Header field in the last extension header of the packet.",,,"See [RFC791] for the specification of the IPv4 protocol field. See [RFC8200] for the specification of the IPv6 protocol field. See the list of protocol numbers assigned by IANA at [https://www.iana.org/assignments/protocol-numbers].",[RFC5102],0,2013-02-18 5,ipClassOfService,unsigned8,identifier,current,"For IPv4 packets, this is the value of the TOS field in the IPv4 packet header. For IPv6 packets, this is the value of the Traffic Class field in the IPv6 packet header.",,,"See [RFC1812] (Section 5.3.2) and [RFC791] for the definition of the IPv4 TOS field. See [RFC8200] for the definition of the IPv6 Traffic Class field.",[RFC5102],0,2013-02-18 6,tcpControlBits,unsigned16,flags,current,"TCP control bits observed for the packets of this Flow. This information is encoded as a bit field; for each TCP control bit, there is a bit in this set. The bit is set to 1 if any observed packet of this Flow has the corresponding TCP control bit set to 1. The bit is cleared to 0 otherwise. The values of each bit are shown below, per the definition of the bits in the TCP header [RFC9293][RFC3168]: MSb LSb 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | N | C | E | U | A | P | R | S | F | | Zero | Future | S | W | C | R | C | S | S | Y | I | | (Data Offset) | Use | | R | E | G | K | H | T | N | N | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ bit flag value name description ------+-----+------------------------------------- 0x8000 Zero (see tcpHeaderLength) 0x4000 Zero (see tcpHeaderLength) 0x2000 Zero (see tcpHeaderLength) 0x1000 Zero (see tcpHeaderLength) 0x0800 Future Use 0x0400 Future Use 0x0200 Future Use 0x0100 NS ECN Nonce Sum 0x0080 CWR Congestion Window Reduced 0x0040 ECE ECN Echo 0x0020 URG Urgent Pointer field significant 0x0010 ACK Acknowledgment field significant 0x0008 PSH Push Function 0x0004 RST Reset the connection 0x0002 SYN Synchronize sequence numbers 0x0001 FIN No more data from sender As the most significant 4 bits of octets 12 and 13 (counting from zero) of the TCP header [RFC9293] are used to encode the TCP data offset (header length), the corresponding bits in this Information Element MUST be exported as zero and MUST be ignored by the collector. Use the tcpHeaderLength Information Element to encode this value. Each of the 3 bits (0x800, 0x400, and 0x200), which are reserved for future use in [RFC9293], SHOULD be exported as observed in the TCP headers of the packets of this Flow. If exported as a single octet with reduced-size encoding, this Information Element covers the low-order octet of this field (i.e, bits 0x80 to 0x01), omitting the ECN Nonce Sum and the three Future Use bits. A collector receiving this Information Element with reduced-size encoding must not assume anything about the content of these four bits. Exporting Processes exporting this Information Element on behalf of a Metering Process that is not capable of observing any of the ECN Nonce Sum or Future Use bits SHOULD use reduced-size encoding, and only export the least significant 8 bits of this Information Element. Note that previous revisions of this Information Element's definition specified that the CWR and ECE bits must be exported as zero, even if observed. Collectors should therefore not assume that a value of zero for these bits in this Information Element indicates the bits were never set in the observed traffic, especially if these bits are zero in every Flow Record sent by a given exporter.",,,[RFC9293][RFC3168],[RFC7125],1,2014-01-03 7,sourceTransportPort,unsigned16,identifier,current,"The source port identifier in the transport header. For the transport protocols UDP, TCP, and SCTP, this is the source port number given in the respective header. This field MAY also be used for future transport protocols that have 16-bit source port identifiers.",,,"See [RFC768] for the definition of the UDP source port field. See [RFC9293] for the definition of the TCP source port field. See [RFC9260] for the definition of SCTP. Additional information on defined UDP and TCP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",[RFC5102],0,2013-02-18 8,sourceIPv4Address,ipv4Address,default,current,The IPv4 source address in the IP packet header.,,,"See [RFC791] for the definition of the IPv4 source address field.",[RFC5102],1,2014-02-03 9,sourceIPv4PrefixLength,unsigned8,,current,"The number of contiguous bits that are relevant in the sourceIPv4Prefix Information Element.",bits,0-32,,[RFC5102],0,2013-02-18 10,ingressInterface,unsigned32,identifier,current,"The index of the IP interface where packets of this Flow are being received. The value matches the value of managed object 'ifIndex' as defined in [RFC2863]. Note that ifIndex values are not assigned statically to an interface and that the interfaces may be renumbered every time the device's management system is re-initialized, as specified in [RFC2863].",,,"See [RFC2863] for the definition of the ifIndex object.",[RFC5102],0,2013-02-18 11,destinationTransportPort,unsigned16,identifier,current,"The destination port identifier in the transport header. For the transport protocols UDP, TCP, and SCTP, this is the destination port number given in the respective header. This field MAY also be used for future transport protocols that have 16-bit destination port identifiers.",,,"See [RFC768] for the definition of the UDP destination port field. See [RFC9293] for the definition of the TCP destination port field. See [RFC9260] for the definition of SCTP. Additional information on defined UDP and TCP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",[RFC5102],0,2013-02-18 12,destinationIPv4Address,ipv4Address,default,current,The IPv4 destination address in the IP packet header.,,,"See [RFC791] for the definition of the IPv4 destination address field.",[RFC5102],1,2014-02-03 13,destinationIPv4PrefixLength,unsigned8,,current,"The number of contiguous bits that are relevant in the destinationIPv4Prefix Information Element.",bits,0-32,,[RFC5102],0,2013-02-18 14,egressInterface,unsigned32,identifier,current,"The index of the IP interface where packets of this Flow are being sent. The value matches the value of managed object 'ifIndex' as defined in [RFC2863]. Note that ifIndex values are not assigned statically to an interface and that the interfaces may be renumbered every time the device's management system is re-initialized, as specified in [RFC2863].",,,"See [RFC2863] for the definition of the ifIndex object.",[RFC5102],0,2013-02-18 15,ipNextHopIPv4Address,ipv4Address,default,current,The IPv4 address of the next IPv4 hop.,,,,[RFC5102],1,2014-02-03 16,bgpSourceAsNumber,unsigned32,identifier,current,"The autonomous system (AS) number of the source IP address. If AS path information for this Flow is only available as an unordered AS set (and not as an ordered AS sequence), then the value of this Information Element is 0.",,,"See [RFC4271] for a description of BGP-4, and see [RFC1930] for the definition of the AS number.",[RFC5102],0,2013-02-18 17,bgpDestinationAsNumber,unsigned32,identifier,current,"The autonomous system (AS) number of the destination IP address. If AS path information for this Flow is only available as an unordered AS set (and not as an ordered AS sequence), then the value of this Information Element is 0.",,,"See [RFC4271] for a description of BGP-4, and see [RFC1930] for the definition of the AS number.",[RFC5102],0,2013-02-18 18,bgpNextHopIPv4Address,ipv4Address,default,current,The IPv4 address of the next (adjacent) BGP hop.,,,See [RFC4271] for a description of BGP-4.,[RFC5102],1,2014-02-03 19,postMCastPacketDeltaCount,unsigned64,deltaCounter,current,"The number of outgoing multicast packets since the previous report (if any) sent for packets of this Flow by a multicast daemon within the Observation Domain. This property cannot necessarily be observed at the Observation Point, but may be retrieved by other means.",packets,,,[RFC5102],0,2013-02-18 20,postMCastOctetDeltaCount,unsigned64,deltaCounter,current,"The number of octets since the previous report (if any) in outgoing multicast packets sent for packets of this Flow by a multicast daemon within the Observation Domain. This property cannot necessarily be observed at the Observation Point, but may be retrieved by other means. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 21,flowEndSysUpTime,unsigned32,,current,"The relative timestamp of the last packet of this Flow. It indicates the number of milliseconds since the last (re-)initialization of the IPFIX Device (sysUpTime). sysUpTime can be calculated from systemInitTimeMilliseconds.",milliseconds,,,[RFC5102],1,2014-01-11 22,flowStartSysUpTime,unsigned32,,current,"The relative timestamp of the first packet of this Flow. It indicates the number of milliseconds since the last (re-)initialization of the IPFIX Device (sysUpTime). sysUpTime can be calculated from systemInitTimeMilliseconds.",milliseconds,,,[RFC5102],1,2014-01-11 23,postOctetDeltaCount,unsigned64,deltaCounter,current,"The definition of this Information Element is identical to the definition of Information Element 'octetDeltaCount', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",octets,,,[RFC5102],0,2013-02-18 24,postPacketDeltaCount,unsigned64,deltaCounter,current,"The definition of this Information Element is identical to the definition of Information Element 'packetDeltaCount', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",packets,,,[RFC5102],0,2013-02-18 25,minimumIpTotalLength,unsigned64,,current,"Length of the smallest packet observed for this Flow. The packet length includes the IP header(s) length and the IP payload length.",octets,,"See [RFC791] for the specification of the IPv4 total length. See [RFC8200] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload length.",[RFC5102],0,2013-02-18 26,maximumIpTotalLength,unsigned64,,current,"Length of the largest packet observed for this Flow. The packet length includes the IP header(s) length and the IP payload length.",octets,,"See [RFC791] for the specification of the IPv4 total length. See [RFC8200] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload length.",[RFC5102],0,2013-02-18 27,sourceIPv6Address,ipv6Address,default,current,The IPv6 source address in the IP packet header.,,,"See [RFC8200] for the definition of the Source Address field in the IPv6 header.",[RFC5102],1,2014-02-03 28,destinationIPv6Address,ipv6Address,default,current,The IPv6 destination address in the IP packet header.,,,"See [RFC8200] for the definition of the Destination Address field in the IPv6 header.",[RFC5102],1,2014-02-03 29,sourceIPv6PrefixLength,unsigned8,,current,"The number of contiguous bits that are relevant in the sourceIPv6Prefix Information Element.",bits,0-128,,[RFC5102],0,2013-02-18 30,destinationIPv6PrefixLength,unsigned8,,current,"The number of contiguous bits that are relevant in the destinationIPv6Prefix Information Element.",bits,0-128,,[RFC5102],0,2013-02-18 31,flowLabelIPv6,unsigned32,identifier,current,The value of the IPv6 Flow Label field in the IP packet header.,,0-0xFFFFF,"See [RFC8200] for the definition of the Flow Label field in the IPv6 packet header.",[RFC5102],1,2014-08-13 32,icmpTypeCodeIPv4,unsigned16,identifier,current,"Type and Code of the IPv4 ICMP message. The combination of both values is reported as (ICMP type * 256) + ICMP code.",,,"See [RFC792] for the definition of the IPv4 ICMP type and code fields.",[RFC5102],0,2013-02-18 33,igmpType,unsigned8,identifier,current,The type field of the IGMP message.,,,"See [RFC3376] for the definition of the IGMP type field.",[RFC5102],0,2013-02-18 34,samplingInterval,unsigned32,quantity,deprecated,"Deprecated in favor of 305 samplingPacketInterval. When using sampled NetFlow, the rate at which packets are sampled -- e.g., a value of 100 indicates that one of every 100 packets is sampled.",packets,,,[RFC7270],0,2014-04-04 35,samplingAlgorithm,unsigned8,identifier,deprecated,"Deprecated in favor of 304 selectorAlgorithm. The type of algorithm used for sampled NetFlow: 1 - Deterministic Sampling, 2 - Random Sampling. The values are not compatible with the selectorAlgorithm IE, where ""Deterministic"" has been replaced by ""Systematic count-based"" (1) or ""Systematic time-based"" (2), and ""Random"" is (3). Conversion is required; see [Packet Sampling (PSAMP) Parameters.]",,,,[RFC7270],0,2014-04-04 36,flowActiveTimeout,unsigned16,,current,"The number of seconds after which an active Flow is timed out anyway, even if there is still a continuous flow of packets.",seconds,,,[RFC5102],0,2013-02-18 37,flowIdleTimeout,unsigned16,,current,"A Flow is considered to be timed out if no packets belonging to the Flow have been observed for the number of seconds specified by this field.",seconds,,,[RFC5102],0,2013-02-18 38,engineType,unsigned8,identifier,deprecated,"Type of flow switching engine in a router/switch: RP = 0, VIP/Line card = 1, PFC/DFC = 2. Reserved for internal use on the Collector.",,,,[RFC7270],0,2014-04-04 39,engineId,unsigned8,identifier,deprecated,"Versatile Interface Processor (VIP) or line card slot number of the flow switching engine in a router/switch. Reserved for internal use on the Collector.",,,,[RFC7270],0,2014-04-04 40,exportedOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets that the Exporting Process has sent since the Exporting Process (re-)initialization to a particular Collecting Process. The value of this Information Element is calculated by summing up the IPFIX Message Header length values of all IPFIX Messages that were successfully sent to the Collecting Process. The reported number excludes octets in the IPFIX Message that carries the counter value. If this Information Element is sent to a particular Collecting Process, then by default it specifies the number of octets sent to this Collecting Process.",octets,,,[RFC5102],0,2013-02-18 41,exportedMessageTotalCount,unsigned64,totalCounter,current,"The total number of IPFIX Messages that the Exporting Process has sent since the Exporting Process (re-)initialization to a particular Collecting Process. The reported number excludes the IPFIX Message that carries the counter value. If this Information Element is sent to a particular Collecting Process, then by default it specifies the number of IPFIX Messages sent to this Collecting Process.",messages,,,[RFC5102],0,2013-02-18 42,exportedFlowRecordTotalCount,unsigned64,totalCounter,current,"The total number of Flow Records that the Exporting Process has sent as Data Records since the Exporting Process (re-)initialization to a particular Collecting Process. The reported number excludes Flow Records in the IPFIX Message that carries the counter value. If this Information Element is sent to a particular Collecting Process, then by default it specifies the number of Flow Records sent to this process.",flows,,,[RFC5102],0,2013-02-18 43,ipv4RouterSc,ipv4Address,default,deprecated,"This is a platform-specific field for the Catalyst 5000/Catalyst 6000 family. It is used to store the address of a router that is being shortcut when performing MultiLayer Switching.",,,[CCO-MLS] describes MultiLayer Switching.,[RFC7270],0,2014-04-04 44,sourceIPv4Prefix,ipv4Address,default,current,IPv4 source address prefix.,,,,[RFC5102],0,2013-02-18 45,destinationIPv4Prefix,ipv4Address,default,current,IPv4 destination address prefix.,,,,[RFC5102],0,2013-02-18 46,mplsTopLabelType,unsigned8,identifier,current,"This field identifies the control protocol that allocated the top-of-stack label. Values for this field are listed in the MPLS label type registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-mpls-label-type]",,,"See [RFC3031] for the MPLS label structure. See the list of MPLS label types assigned by IANA at [https://www.iana.org/assignments/mpls-label-values].",[RFC5102],0,2013-02-18 47,mplsTopLabelIPv4Address,ipv4Address,default,current,"The IPv4 address of the system that the MPLS top label will cause this Flow to be forwarded to.",,,"See [RFC3031] for the association between MPLS labels and IP addresses.",[RFC5102],1,2014-02-03 48,samplerId,unsigned8,identifier,deprecated,"Deprecated in favor of 302 selectorId. The unique identifier associated with samplerName.",,,,[RFC7270],0,2014-04-04 49,samplerMode,unsigned8,identifier,deprecated,"Deprecated in favor of 304 selectorAlgorithm. The values are not compatible: selectorAlgorithm=3 is random sampling. The type of algorithm used for sampling data: 1 - Deterministic, 2 - Random Sampling. Use with samplerRandomInterval.",,,,[RFC7270],0,2014-04-04 50,samplerRandomInterval,unsigned32,quantity,deprecated,"Deprecated in favor of 305 samplingPacketInterval. Packet interval at which to sample -- in case of random sampling. Used in connection with the samplerMode 0x02 (random sampling) value.",,,,[RFC7270],0,2014-04-04 51,classId,unsigned8,identifier,deprecated,"Deprecated in favor of 302 selectorId. Characterizes the traffic class, i.e., QoS treatment.",,,,[RFC7270],0,2014-04-04 52,minimumTTL,unsigned8,,current,Minimum TTL value observed for any packet in this Flow.,hops,,"See [RFC791] for the definition of the IPv4 Time to Live field. See [RFC8200] for the definition of the IPv6 Hop Limit field.",[RFC5102],0,2013-02-18 53,maximumTTL,unsigned8,,current,Maximum TTL value observed for any packet in this Flow.,hops,,"See [RFC791] for the definition of the IPv4 Time to Live field. See [RFC8200] for the definition of the IPv6 Hop Limit field.",[RFC5102],0,2013-02-18 54,fragmentIdentification,unsigned32,identifier,current,"The value of the Identification field in the IPv4 packet header or in the IPv6 Fragment header, respectively. The value is 0 for IPv6 if there is no fragment header.",,,"See [RFC791] for the definition of the IPv4 Identification field. See [RFC8200] for the definition of the Identification field in the IPv6 Fragment header.",[RFC5102],0,2013-02-18 55,postIpClassOfService,unsigned8,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'ipClassOfService', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,"See [RFC791] for the definition of the IPv4 TOS field. See [RFC8200] for the definition of the IPv6 Traffic Class field. See [RFC3234] for the definition of middleboxes.",[RFC5102],0,2013-02-18 56,sourceMacAddress,macAddress,default,current,The IEEE 802 source MAC address field.,,,See IEEE.802-3.2002.,[RFC5102],1,2014-02-03 57,postDestinationMacAddress,macAddress,default,current,"The definition of this Information Element is identical to the definition of Information Element 'destinationMacAddress', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,See IEEE.802-3.2002.,[RFC5102],1,2014-02-03 58,vlanId,unsigned16,identifier,current,"Virtual LAN identifier associated with ingress interface. For dot1q vlans, see 243 dot1qVlanId.",,,See IEEE.802-1Q.2003.,[RFC5102],0,2013-02-18 59,postVlanId,unsigned16,identifier,current,"Virtual LAN identifier associated with egress interface. For postdot1q vlans, see 254, postDot1qVlanId.",,,See IEEE.802-1Q.2003.,[RFC5102],0,2013-02-18 60,ipVersion,unsigned8,identifier,current,The IP version field in the IP packet header.,,,"See [RFC791] for the definition of the version field in the IPv4 packet header. See [RFC8200] for the definition of the version field in the IPv6 packet header. Additional information on defined version numbers can be found at [https://www.iana.org/assignments/version-numbers].",[RFC5102],0,2013-02-18 61,flowDirection,unsigned8,identifier,current,"The direction of the Flow observed at the Observation Point. There are only two values defined. 0x00: ingress flow 0x01: egress flow",,,,[RFC5102],0,2013-02-18 62,ipNextHopIPv6Address,ipv6Address,default,current,The IPv6 address of the next IPv6 hop.,,,,[RFC5102],1,2014-02-03 63,bgpNextHopIPv6Address,ipv6Address,default,current,The IPv6 address of the next (adjacent) BGP hop.,,,See [RFC4271] for a description of BGP-4.,[RFC5102],1,2014-02-03 64,ipv6ExtensionHeaders,unsigned32,flags,current,"IPv6 extension headers observed in packets of this Flow. The information is encoded in a set of bit fields. For each IPv6 option header, there is a bit in this set. The bit is set to 1 if any observed packet of this Flow contains the corresponding IPv6 extension header. Otherwise, if no observed packet of this Flow contained the respective IPv6 extension header, the value of the corresponding bit is 0. 0 1 2 3 4 5 6 7 +-----+-----+-----+-----+-----+-----+-----+-----+ | DST | HOP | Res | UNK |FRA0 | RH |FRA1 | Res | ... +-----+-----+-----+-----+-----+-----+-----+-----+ 8 9 10 11 12 13 14 15 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | Reserved | MOB | ESP | AH | PAY | ... +-----+-----+-----+-----+-----+-----+-----+-----+ 16 17 18 19 20 21 22 23 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | Reserved | ... +-----+-----+-----+-----+-----+-----+-----+-----+ 24 25 26 27 28 29 30 31 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | Reserved | +-----+-----+-----+-----+-----+-----+-----+-----+ Bit IPv6 Option Description 0, DST 60 Destination option header 1, HOP 0 Hop-by-hop option header 2, Res Reserved 3, UNK Unknown Layer 4 header (compressed, encrypted, not supported) 4, FRA0 44 Fragment header - first fragment 5, RH 43 Routing header 6, FRA1 44 Fragmentation header - not first fragment 7, Res Reserved 8 to 11 Reserved 12, MOB 135 IPv6 mobility [RFC3775] 13, ESP 50 Encrypted security payload 14, AH 51 Authentication Header 15, PAY 108 Payload compression header 16 to 31 Reserved",,,"See [RFC8200] for the general definition of IPv6 extension headers and for the specification of the hop-by-hop options header, the routing header, the fragment header, and the destination options header. See [RFC4302] for the specification of the authentication header. See [RFC4303] for the specification of the encapsulating security payload. The diagram provided in [RFC5102] is incorrect. The diagram in this registry is taken from Errata 1738. See [RFC Errata 1738].",[RFC5102],0,2013-02-18 65-69,Assigned for NetFlow v9 compatibility,,,,,,,[RFC3954],[RFC5102],0,2013-02-18 70,mplsTopLabelStackSection,octetArray,default,current,"The Label, Exp, and S fields from the top MPLS label stack entry, i.e., from the last label that was pushed. The size of this Information Element is 3 octets. 0 1 2 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Label | Exp |S| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Label: Label Value, 20 bits Exp: Experimental Use, 3 bits S: Bottom of Stack, 1 bit",,,See [RFC3032].,[RFC5102],1,2014-02-03 71,mplsLabelStackSection2,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsTopLabelStackSection. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 72,mplsLabelStackSection3,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection2. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 73,mplsLabelStackSection4,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection3. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 74,mplsLabelStackSection5,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection4. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 75,mplsLabelStackSection6,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection5. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 76,mplsLabelStackSection7,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection6. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 77,mplsLabelStackSection8,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection7. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 78,mplsLabelStackSection9,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection8. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 79,mplsLabelStackSection10,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection9. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 80,destinationMacAddress,macAddress,default,current,The IEEE 802 destination MAC address field.,,,See IEEE.802-3.2002.,[RFC5102],1,2014-02-03 81,postSourceMacAddress,macAddress,default,current,"The definition of this Information Element is identical to the definition of Information Element 'sourceMacAddress', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,See IEEE.802-3.2002.,[RFC5102],1,2014-02-03 82,interfaceName,string,default,current,"A short name uniquely describing an interface, eg ""Eth1/0"".",,,See [RFC2863] for the definition of the ifName object.,[ipfix-iana_at_cisco.com],0,2013-02-18 83,interfaceDescription,string,default,current,"The description of an interface, eg ""FastEthernet 1/0"" or ""ISP connection"".",,,See [RFC2863] for the definition of the ifDescr object.,[ipfix-iana_at_cisco.com],0,2013-02-18 84,samplerName,string,,deprecated,"Deprecated in favor of 335 selectorName. Name of the flow sampler.",,,,[RFC7270],0,2014-04-04 85,octetTotalCount,unsigned64,totalCounter,current,"The total number of octets in incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 86,packetTotalCount,unsigned64,totalCounter,current,"The total number of incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[RFC5102],0,2013-02-18 87,flagsAndSamplerId,unsigned32,identifier,deprecated,"Flow flags and the value of the sampler ID (samplerId) combined in one bitmapped field. Reserved for internal use on the Collector.",,,,[RFC7270],0,2014-04-04 88,fragmentOffset,unsigned16,quantity,current,"The value of the IP fragment offset field in the IPv4 packet header or the IPv6 Fragment header, respectively. The value is 0 for IPv6 if there is no fragment header.",,0-0x1FFF,"See [RFC791] for the specification of the fragment offset in the IPv4 header. See [RFC8200] for the specification of the fragment offset in the IPv6 Fragment header.",[RFC5102],1,2014-08-13 89,forwardingStatus,unsigned8,identifier,current,"This Information Element describes the forwarding status of the flow and any attached reasons. The layout of the encoding is as follows: MSB - 0 1 2 3 4 5 6 7 - LSB +---+---+---+---+---+---+---+---+ | Status| Reason code or flags | +---+---+---+---+---+---+---+---+ See the Forwarding Status sub-registries at [https://www.iana.org/assignments/ipfix/ipfix.xhtml#forwarding-status]. Examples: value : 0x40 = 64 binary: 01000000 decode: 01 -> Forward 000000 -> No further information value : 0x89 = 137 binary: 10001001 decode: 10 -> Drop 001001 -> Bad TTL",,,"See ""NetFlow Version 9 Flow-Record Format"" [CCO-NF9FMT].",[RFC7270][RFC Errata 5262],2,2018-02-21 90,mplsVpnRouteDistinguisher,octetArray,default,current,"The value of the VPN route distinguisher of a corresponding entry in a VPN routing and forwarding table. Route distinguisher ensures that the same address can be used in several different MPLS VPNs and that it is possible for BGP to carry several completely different routes to that address, one for each VPN. According to [RFC4364], the size of mplsVpnRouteDistinguisher is 8 octets. However, in [RFC4382] an octet string with flexible length was chosen for representing a VPN route distinguisher by object MplsL3VpnRouteDistinguisher. This choice was made in order to be open to future changes of the size. This idea was adopted when choosing octetArray as abstract data type for this Information Element. The maximum length of this Information Element is 256 octets.",,,"See [RFC4364] for the specification of the route distinguisher. See [RFC4382] for the specification of the MPLS/BGP Layer 3 Virtual Private Network (VPN) Management Information Base.",[RFC5102],1,2014-02-03 91,mplsTopLabelPrefixLength,unsigned8,quantity,current,"The prefix length of the subnet of the mplsTopLabelIPv4Address that the MPLS top label will cause the Flow to be forwarded to.",bits,0-32,"See [RFC3031] for the association between MPLS labels and prefix lengths.",[ipfix-iana_at_cisco.com],1,2014-08-13 92,srcTrafficIndex,unsigned32,identifier,current,BGP Policy Accounting Source Traffic Index.,,,BGP policy accounting as described in [CCO-BGPPOL].,[RFC7270],0,2014-04-04 93,dstTrafficIndex,unsigned32,identifier,current,BGP Policy Accounting Destination Traffic Index.,,,BGP policy accounting as described in [CCO-BGPPOL].,[RFC7270],0,2014-04-04 94,applicationDescription,string,default,current,Specifies the description of an application.,,,,[RFC6759],1,2014-02-03 95,applicationId,octetArray,default,current,Specifies an Application ID per [RFC6759].,,,See section 4 of [RFC6759] for the applicationId Information Element Specification.,[RFC6759],1,2014-02-03 96,applicationName,string,default,current,Specifies the name of an application.,,,,[RFC6759],0,2013-02-18 97,Assigned for NetFlow v9 compatibility,,,,,,,[RFC3954],[RFC5102],0,2013-02-18 98,postIpDiffServCodePoint,unsigned8,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'ipDiffServCodePoint', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,0-63,"See [RFC3260] for the definition of the Differentiated Services Field. See section 5.3.2 of [RFC1812] and [RFC791] for the definition of the IPv4 TOS field. See [RFC8200] for the definition of the IPv6 Traffic Class field. See the IPFIX Information Model [RFC5102] for the 'ipDiffServCodePoint' specification.",[ipfix-iana_at_cisco.com],0,2013-02-18 99,multicastReplicationFactor,unsigned32,quantity,current,"The amount of multicast replication that's applied to a traffic stream.",,,"See [RFC1112] for the specification of reserved IPv4 multicast addresses. See [RFC4291] for the specification of reserved IPv6 multicast addresses.",[ipfix-iana_at_cisco.com],0,2013-02-18 100,className,string,,deprecated,"Deprecated in favor of 335 selectorName. Traffic Class Name, associated with the classId Information Element.",,,,[RFC7270],0,2014-04-04 101,classificationEngineId,unsigned8,identifier,current,"A unique identifier for the engine that determined the Selector ID. Thus, the Classification Engine ID defines the context for the Selector ID. The Classification Engine can be considered a specific registry for application assignments. Values for this field are listed in the Classification Engine IDs registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#classification-engine-ids]",,,,[RFC6759],0,2013-02-18 102,layer2packetSectionOffset,unsigned16,quantity,deprecated,"Deprecated in favor of 409 sectionOffset. Layer 2 packet section offset. Potentially a generic packet section offset.",,,,[RFC7270],0,2014-04-04 103,layer2packetSectionSize,unsigned16,quantity,deprecated,"Deprecated in favor of 312 dataLinkFrameSize. Layer 2 packet section size. Potentially a generic packet section size.",,,,[RFC7270],0,2014-04-04 104,layer2packetSectionData,octetArray,,deprecated,"Deprecated in favor of 315 dataLinkFrameSection. Layer 2 packet section data.",,,,[RFC7270],0,2014-04-04 105-127,Assigned for NetFlow v9 compatibility,,,,,,,[RFC3954],[RFC5102],0,2013-02-18 128,bgpNextAdjacentAsNumber,unsigned32,identifier,current,"The autonomous system (AS) number of the first AS in the AS path to the destination IP address. The path is deduced by looking up the destination IP address of the Flow in the BGP routing information base. If AS path information for this Flow is only available as an unordered AS set (and not as an ordered AS sequence), then the value of this Information Element is 0.",,,"See [RFC4271] for a description of BGP-4, and see [RFC1930] for the definition of the AS number.",[RFC5102],0,2013-02-18 129,bgpPrevAdjacentAsNumber,unsigned32,identifier,current,"The autonomous system (AS) number of the last AS in the AS path from the source IP address. The path is deduced by looking up the source IP address of the Flow in the BGP routing information base. If AS path information for this Flow is only available as an unordered AS set (and not as an ordered AS sequence), then the value of this Information Element is 0. In case of BGP asymmetry, the bgpPrevAdjacentAsNumber might not be able to report the correct value.",,,"See [RFC4271] for a description of BGP-4, and see [RFC1930] for the definition of the AS number.",[RFC5102],0,2013-02-18 130,exporterIPv4Address,ipv4Address,default,current,"The IPv4 address used by the Exporting Process. This is used by the Collector to identify the Exporter in cases where the identity of the Exporter may have been obscured by the use of a proxy.",,,,[RFC5102],1,2014-02-03 131,exporterIPv6Address,ipv6Address,default,current,"The IPv6 address used by the Exporting Process. This is used by the Collector to identify the Exporter in cases where the identity of the Exporter may have been obscured by the use of a proxy.",,,,[RFC5102],1,2014-02-03 132,droppedOctetDeltaCount,unsigned64,deltaCounter,current,"The number of octets since the previous report (if any) in packets of this Flow dropped by packet treatment. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 133,droppedPacketDeltaCount,unsigned64,deltaCounter,current,"The number of packets since the previous report (if any) of this Flow dropped by packet treatment.",packets,,,[RFC5102],0,2013-02-18 134,droppedOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in packets of this Flow dropped by packet treatment since the Metering Process (re-)initialization for this Observation Point. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 135,droppedPacketTotalCount,unsigned64,totalCounter,current,"The number of packets of this Flow dropped by packet treatment since the Metering Process (re-)initialization for this Observation Point.",packets,,,[RFC5102],0,2013-02-18 136,flowEndReason,unsigned8,identifier,current,The reason for Flow termination. Values are listed in the flowEndReason registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flow-end-reason].,,,,[RFC5102],0,2013-02-18 137,commonPropertiesId,unsigned64,identifier,current,"An identifier of a set of common properties that is unique per Observation Domain and Transport Session. Typically, this Information Element is used to link to information reported in separate Data Records.",,,,[RFC5102],0,2013-02-18 138,observationPointId,unsigned64,identifier,current,"An identifier of an Observation Point that is unique per Observation Domain. It is RECOMMENDED that this identifier is also unique per IPFIX Device. Typically, this Information Element is used for limiting the scope of other Information Elements.",,,,[RFC5102][ipfix-iana_at_cisco.com],1,2013-04-11 139,icmpTypeCodeIPv6,unsigned16,identifier,current,"Type and Code of the IPv6 ICMP message. The combination of both values is reported as (ICMP type * 256) + ICMP code.",,,"See [RFC4443] for the definition of the IPv6 ICMP type and code fields.",[RFC5102],0,2013-02-18 140,mplsTopLabelIPv6Address,ipv6Address,default,current,"The IPv6 address of the system that the MPLS top label will cause this Flow to be forwarded to.",,,"See [RFC3031] for the association between MPLS labels and IP addresses.",[RFC5102],1,2014-02-03 141,lineCardId,unsigned32,identifier,current,"An identifier of a line card that is unique per IPFIX Device hosting an Observation Point. Typically, this Information Element is used for limiting the scope of other Information Elements.",,,,[RFC5102],0,2013-02-18 142,portId,unsigned32,identifier,current,"An identifier of a line port that is unique per IPFIX Device hosting an Observation Point. Typically, this Information Element is used for limiting the scope of other Information Elements.",,,,[RFC5102],0,2013-02-18 143,meteringProcessId,unsigned32,identifier,current,"An identifier of a Metering Process that is unique per IPFIX Device. Typically, this Information Element is used for limiting the scope of other Information Elements. Note that process identifiers are typically assigned dynamically. The Metering Process may be re-started with a different ID.",,,,[RFC5102],0,2013-02-18 144,exportingProcessId,unsigned32,identifier,current,"An identifier of an Exporting Process that is unique per IPFIX Device. Typically, this Information Element is used for limiting the scope of other Information Elements. Note that process identifiers are typically assigned dynamically. The Exporting Process may be re-started with a different ID.",,,,[RFC5102],0,2013-02-18 145,templateId,unsigned16,identifier,current,"An identifier of a Template that is locally unique within a combination of a Transport session and an Observation Domain. Template IDs 0-255 are reserved for Template Sets, Options Template Sets, and other reserved Sets yet to be created. Template IDs of Data Sets are numbered from 256 to 65535. Typically, this Information Element is used for limiting the scope of other Information Elements. Note that after a re-start of the Exporting Process Template identifiers may be re-assigned.",,,,[RFC5102],0,2013-02-18 146,wlanChannelId,unsigned8,identifier,current,The identifier of the 802.11 (Wi-Fi) channel used.,,,See IEEE.802-11.1999.,[RFC5102],0,2013-02-18 147,wlanSSID,string,default,current,"The Service Set IDentifier (SSID) identifying an 802.11 (Wi-Fi) network used. According to IEEE.802-11.1999, the SSID is encoded into a string of up to 32 characters.",,,See IEEE.802-11.1999.,[RFC5102],0,2013-02-18 148,flowId,unsigned64,identifier,current,"An identifier of a Flow that is unique within an Observation Domain. This Information Element can be used to distinguish between different Flows if Flow Keys such as IP addresses and port numbers are not reported or are reported in separate records.",,,,[RFC5102],0,2013-02-18 149,observationDomainId,unsigned32,identifier,current,"An identifier of an Observation Domain that is locally unique to an Exporting Process. The Exporting Process uses the Observation Domain ID to uniquely identify to the Collecting Process the Observation Domain where Flows were metered. It is RECOMMENDED that this identifier is also unique per IPFIX Device. A value of 0 indicates that no specific Observation Domain is identified by this Information Element. Typically, this Information Element is used for limiting the scope of other Information Elements.",,,,[RFC5102],0,2013-02-18 150,flowStartSeconds,dateTimeSeconds,default,current,The absolute timestamp of the first packet of this Flow.,seconds,,,[RFC5102],0,2013-02-18 151,flowEndSeconds,dateTimeSeconds,default,current,The absolute timestamp of the last packet of this Flow.,seconds,,,[RFC5102],0,2013-02-18 152,flowStartMilliseconds,dateTimeMilliseconds,default,current,The absolute timestamp of the first packet of this Flow.,milliseconds,,,[RFC5102],0,2013-02-18 153,flowEndMilliseconds,dateTimeMilliseconds,default,current,The absolute timestamp of the last packet of this Flow.,milliseconds,,,[RFC5102],0,2013-02-18 154,flowStartMicroseconds,dateTimeMicroseconds,default,current,The absolute timestamp of the first packet of this Flow.,microseconds,,,[RFC5102],0,2013-02-18 155,flowEndMicroseconds,dateTimeMicroseconds,default,current,The absolute timestamp of the last packet of this Flow.,microseconds,,,[RFC5102],0,2013-02-18 156,flowStartNanoseconds,dateTimeNanoseconds,default,current,The absolute timestamp of the first packet of this Flow.,nanoseconds,,,[RFC5102],0,2013-02-18 157,flowEndNanoseconds,dateTimeNanoseconds,default,current,The absolute timestamp of the last packet of this Flow.,nanoseconds,,,[RFC5102],0,2013-02-18 158,flowStartDeltaMicroseconds,unsigned32,,current,"This is a relative timestamp only valid within the scope of a single IPFIX Message. It contains the negative time offset of the first observed packet of this Flow relative to the export time specified in the IPFIX Message Header.",microseconds,,"See the [IPFIX protocol specification] for the definition of the IPFIX Message Header.",[RFC5102],0,2013-02-18 159,flowEndDeltaMicroseconds,unsigned32,,current,"This is a relative timestamp only valid within the scope of a single IPFIX Message. It contains the negative time offset of the last observed packet of this Flow relative to the export time specified in the IPFIX Message Header.",microseconds,,"See the [IPFIX protocol specification] for the definition of the IPFIX Message Header.",[RFC5102],0,2013-02-18 160,systemInitTimeMilliseconds,dateTimeMilliseconds,default,current,"The absolute timestamp of the last (re-)initialization of the IPFIX Device.",milliseconds,,,[RFC5102],0,2013-02-18 161,flowDurationMilliseconds,unsigned32,,current,"The difference in time between the first observed packet of this Flow and the last observed packet of this Flow.",milliseconds,,,[RFC5102],0,2013-02-18 162,flowDurationMicroseconds,unsigned32,,current,"The difference in time between the first observed packet of this Flow and the last observed packet of this Flow.",microseconds,,,[RFC5102],0,2013-02-18 163,observedFlowTotalCount,unsigned64,totalCounter,current,"The total number of Flows observed in the Observation Domain since the Metering Process (re-)initialization for this Observation Point.",flows,,,[RFC5102],0,2013-02-18 164,ignoredPacketTotalCount,unsigned64,totalCounter,current,"The total number of observed IP packets that the Metering Process did not process since the (re-)initialization of the Metering Process.",packets,,,[RFC5102],0,2013-02-18 165,ignoredOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in observed IP packets (including the IP header) that the Metering Process did not process since the (re-)initialization of the Metering Process.",octets,,,[RFC5102],0,2013-02-18 166,notSentFlowTotalCount,unsigned64,totalCounter,current,"The total number of Flow Records that were generated by the Metering Process and dropped by the Metering Process or by the Exporting Process instead of being sent to the Collecting Process. There are several potential reasons for this including resource shortage and special Flow export policies.",flows,,,[RFC5102],0,2013-02-18 167,notSentPacketTotalCount,unsigned64,totalCounter,current,"The total number of packets in Flow Records that were generated by the Metering Process and dropped by the Metering Process or by the Exporting Process instead of being sent to the Collecting Process. There are several potential reasons for this including resource shortage and special Flow export policies.",packets,,,[RFC5102],0,2013-02-18 168,notSentOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in packets in Flow Records that were generated by the Metering Process and dropped by the Metering Process or by the Exporting Process instead of being sent to the Collecting Process. There are several potential reasons for this including resource shortage and special Flow export policies.",octets,,,[RFC5102],0,2013-02-18 169,destinationIPv6Prefix,ipv6Address,default,current,IPv6 destination address prefix.,,,,[RFC5102],0,2013-02-18 170,sourceIPv6Prefix,ipv6Address,default,current,IPv6 source address prefix.,,,,[RFC5102],0,2013-02-18 171,postOctetTotalCount,unsigned64,totalCounter,current,"The definition of this Information Element is identical to the definition of Information Element 'octetTotalCount', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",octets,,,[RFC5102],0,2013-02-18 172,postPacketTotalCount,unsigned64,totalCounter,current,"The definition of this Information Element is identical to the definition of Information Element 'packetTotalCount', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",packets,,,[RFC5102],0,2013-02-18 173,flowKeyIndicator,unsigned64,flags,current,"This set of bit fields is used for marking the Information Elements of a Data Record that serve as Flow Key. Each bit represents an Information Element in the Data Record, with the n-th least significant bit representing the n-th Information Element. A bit set to value 1 indicates that the corresponding Information Element is a Flow Key of the reported Flow. A bit set to value 0 indicates that this is not the case. If the Data Record contains more than 64 Information Elements, the corresponding Template SHOULD be designed such that all Flow Keys are among the first 64 Information Elements, because the flowKeyIndicator only contains 64 bits. If the Data Record contains less than 64 Information Elements, then the bits in the flowKeyIndicator for which no corresponding Information Element exists MUST have the value 0.",,,,[RFC5102][RFC Errata 4984],1,2017-08-01 174,postMCastPacketTotalCount,unsigned64,totalCounter,current,"The total number of outgoing multicast packets sent for packets of this Flow by a multicast daemon within the Observation Domain since the Metering Process (re-)initialization. This property cannot necessarily be observed at the Observation Point, but may be retrieved by other means.",packets,,,[RFC5102],0,2013-02-18 175,postMCastOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in outgoing multicast packets sent for packets of this Flow by a multicast daemon in the Observation Domain since the Metering Process (re-)initialization. This property cannot necessarily be observed at the Observation Point, but may be retrieved by other means. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 176,icmpTypeIPv4,unsigned8,identifier,current,Type of the IPv4 ICMP message.,,,"See [RFC792] for the definition of the IPv4 ICMP type field.",[RFC5102],0,2013-02-18 177,icmpCodeIPv4,unsigned8,identifier,current,Code of the IPv4 ICMP message.,,,"See [RFC792] for the definition of the IPv4 ICMP code field.",[RFC5102],0,2013-02-18 178,icmpTypeIPv6,unsigned8,identifier,current,Type of the IPv6 ICMP message.,,,"See [RFC4443] for the definition of the IPv6 ICMP type field.",[RFC5102],0,2013-02-18 179,icmpCodeIPv6,unsigned8,identifier,current,Code of the IPv6 ICMP message.,,,"See [RFC4443] for the definition of the IPv6 ICMP code field.",[RFC5102],0,2013-02-18 180,udpSourcePort,unsigned16,identifier,current,The source port identifier in the UDP header.,,,"See [RFC768] for the definition of the UDP source port field. Additional information on defined UDP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",[RFC5102],0,2013-02-18 181,udpDestinationPort,unsigned16,identifier,current,The destination port identifier in the UDP header.,,,"See [RFC768] for the definition of the UDP destination port field. Additional information on defined UDP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",[RFC5102],0,2013-02-18 182,tcpSourcePort,unsigned16,identifier,current,The source port identifier in the TCP header.,,,"See [RFC9293] for the definition of the TCP source port field. Additional information on defined TCP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",[RFC5102],0,2013-02-18 183,tcpDestinationPort,unsigned16,identifier,current,The destination port identifier in the TCP header.,,,"See [RFC9293] for the definition of the TCP destination port field. Additional information on defined TCP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",[RFC5102],0,2013-02-18 184,tcpSequenceNumber,unsigned32,,current,The sequence number in the TCP header.,,,"See [RFC9293] for the definition of the TCP sequence number.",[RFC5102],0,2013-02-18 185,tcpAcknowledgementNumber,unsigned32,,current,The acknowledgement number in the TCP header.,,,"See [RFC9293] for the definition of the TCP acknowledgement number.",[RFC5102],0,2013-02-18 186,tcpWindowSize,unsigned16,,current,"The window field in the TCP header. If the TCP window scale is supported, then TCP window scale must be known to fully interpret the value of this information.",,,"See [RFC9293] for the definition of the TCP window field. See [RFC1323] for the definition of the TCP window scale.",[RFC5102],0,2013-02-18 187,tcpUrgentPointer,unsigned16,,current,The urgent pointer in the TCP header.,,,"See [RFC9293] for the definition of the TCP urgent pointer.",[RFC5102],0,2013-02-18 188,tcpHeaderLength,unsigned8,,current,"The length of the TCP header. Note that the value of this Information Element is different from the value of the Data Offset field in the TCP header. The Data Offset field indicates the length of the TCP header in units of 4 octets. This Information Elements specifies the length of the TCP header in units of octets.",octets,,"See [RFC9293] for the definition of the TCP header.",[RFC5102],0,2013-02-18 189,ipHeaderLength,unsigned8,,current,"The length of the IP header. For IPv6, the value of this Information Element is 40.",octets,,"See [RFC791] for the definition of the IPv4 header. See [RFC8200] for the definition of the IPv6 header.",[RFC5102],0,2013-02-18 190,totalLengthIPv4,unsigned16,,current,The total length of the IPv4 packet.,octets,,"See [RFC791] for the specification of the IPv4 total length.",[RFC5102],0,2013-02-18 191,payloadLengthIPv6,unsigned16,,current,"This Information Element reports the value of the Payload Length field in the IPv6 header. Note that IPv6 extension headers belong to the payload. Also note that in case of a jumbo payload option the value of the Payload Length field in the IPv6 header is zero and so will be the value reported by this Information Element.",octets,,"See [RFC8200] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload option.",[RFC5102],0,2013-02-18 192,ipTTL,unsigned8,,current,"For IPv4, the value of the Information Element matches the value of the Time to Live (TTL) field in the IPv4 packet header. For IPv6, the value of the Information Element matches the value of the Hop Limit field in the IPv6 packet header.",hops,,"See [RFC791] for the definition of the IPv4 Time to Live field. See [RFC2675] for the definition of the IPv6 Hop Limit field.",[RFC5102],0,2013-02-18 193,nextHeaderIPv6,unsigned8,,current,"The value of the Next Header field of the IPv6 header. The value identifies the type of the following IPv6 extension header or of the following IP payload. Valid values are defined in the IANA Protocol Numbers registry.",,,"See [RFC8200] for the definition of the IPv6 Next Header field. See the list of protocol numbers assigned by IANA at [https://www.iana.org/assignments/protocol-numbers].",[RFC5102],0,2013-02-18 194,mplsPayloadLength,unsigned32,,current,The size of the MPLS packet without the label stack.,octets,,"See [RFC3031] for the specification of MPLS packets. See [RFC3032] for the specification of the MPLS label stack.",[RFC5102],0,2013-02-18 195,ipDiffServCodePoint,unsigned8,identifier,current,"The value of a Differentiated Services Code Point (DSCP) encoded in the Differentiated Services field. The Differentiated Services field spans the most significant 6 bits of the IPv4 TOS field or the IPv6 Traffic Class field, respectively. This Information Element encodes only the 6 bits of the Differentiated Services field. Therefore, its value may range from 0 to 63.",,0-63,"See [RFC3260] for the definition of the Differentiated Services field. See [RFC1812] (Section 5.3.2) and [RFC791] for the definition of the IPv4 TOS field. See [RFC8200] for the definition of the IPv6 Traffic Class field.",[RFC5102],0,2013-02-18 196,ipPrecedence,unsigned8,identifier,current,"The value of the IP Precedence. The IP Precedence value is encoded in the first 3 bits of the IPv4 TOS field or the IPv6 Traffic Class field, respectively. This Information Element encodes only these 3 bits. Therefore, its value may range from 0 to 7.",,0-7,"See [RFC1812] (Section 5.3.3) and [RFC791] for the definition of the IP Precedence. See [RFC1812] (Section 5.3.2) and [RFC791] for the definition of the IPv4 TOS field. See [RFC8200] for the definition of the IPv6 Traffic Class field.",[RFC5102],0,2013-02-18 197,fragmentFlags,unsigned8,flags,current,"Fragmentation properties indicated by flags in the IPv4 packet header or the IPv6 Fragment header, respectively. Bit 0: (RS) Reserved. The value of this bit MUST be 0 until specified otherwise. Bit 1: (DF) 0 = May Fragment, 1 = Don't Fragment. Corresponds to the value of the DF flag in the IPv4 header. Will always be 0 for IPv6 unless a ""don't fragment"" feature is introduced to IPv6. Bit 2: (MF) 0 = Last Fragment, 1 = More Fragments. Corresponds to the MF flag in the IPv4 header or to the M flag in the IPv6 Fragment header, respectively. The value is 0 for IPv6 if there is no fragment header. Bits 3-7: (DC) Don't Care. The values of these bits are irrelevant. 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | R | D | M | D | D | D | D | D | | S | F | F | C | C | C | C | C | +---+---+---+---+---+---+---+---+",,,"See [RFC791] for the specification of the IPv4 fragment flags. See [RFC8200] for the specification of the IPv6 Fragment header.",[RFC5102],0,2013-02-18 198,octetDeltaSumOfSquares,unsigned64,,current,"The sum of the squared numbers of octets per incoming packet since the previous report (if any) for this Flow at the Observation Point. The number of octets includes IP header(s) and IP payload.",,,,[RFC5102],0,2013-02-18 199,octetTotalSumOfSquares,unsigned64,,current,"The total sum of the squared numbers of octets in incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 200,mplsTopLabelTTL,unsigned8,,current,"The TTL field from the top MPLS label stack entry, i.e., the last label that was pushed.",hops,,"See [RFC3032] for the specification of the TTL field.",[RFC5102],0,2013-02-18 201,mplsLabelStackLength,unsigned32,,current,The length of the MPLS label stack in units of octets.,octets,,"See [RFC3032] for the specification of the MPLS label stack.",[RFC5102],0,2013-02-18 202,mplsLabelStackDepth,unsigned32,,current,The number of labels in the MPLS label stack.,entries,,"See [RFC3032] for the specification of the MPLS label stack.",[RFC5102],0,2013-02-18 203,mplsTopLabelExp,unsigned8,flags,current,"The Exp field from the top MPLS label stack entry, i.e., the last label that was pushed. Bits 0-4: Don't Care, value is irrelevant. Bits 5-7: MPLS Exp field. 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | don't care | Exp | +---+---+---+---+---+---+---+---+",,,"See [RFC3032] for the specification of the Exp field. See [RFC3270] for usage of the Exp field.",[RFC5102],0,2013-02-18 204,ipPayloadLength,unsigned32,,current,"The effective length of the IP payload. For IPv4 packets, the value of this Information Element is the difference between the total length of the IPv4 packet (as reported by Information Element totalLengthIPv4) and the length of the IPv4 header (as reported by Information Element headerLengthIPv4). For IPv6, the value of the Payload Length field in the IPv6 header is reported except in the case that the value of this field is zero and that there is a valid jumbo payload option. In this case, the value of the Jumbo Payload Length field in the jumbo payload option is reported.",octets,,"See [RFC791] for the specification of IPv4 packets. See [RFC8200] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload length.",[RFC5102],0,2013-02-18 205,udpMessageLength,unsigned16,,current,The value of the Length field in the UDP header.,octets,,"See [RFC768] for the specification of the UDP header.",[RFC5102],0,2013-02-18 206,isMulticast,unsigned8,flags,current,"If the IP destination address is not a reserved multicast address, then the value of all bits of the octet (including the reserved ones) is zero. The first bit of this octet is set to 1 if the Version field of the IP header has the value 4 and if the Destination Address field contains a reserved multicast address in the range from 224.0.0.0 to 239.255.255.255. Otherwise, this bit is set to 0. The second and third bits of this octet are reserved for future use. The remaining bits of the octet are only set to values other than zero if the IP Destination Address is a reserved IPv6 multicast address. Then the fourth bit of the octet is set to the value of the T flag in the IPv6 multicast address and the remaining four bits are set to the value of the scope field in the IPv6 multicast address. 0 1 2 3 4 5 6 7 +------+------+------+------+------+------+------+------+ | IPv6 multicast scope | T | RES. | RES. | MCv4 | +------+------+------+------+------+------+------+------+ Bits 0-3: set to value of multicast scope if IPv6 multicast Bit 4: set to value of T flag, if IPv6 multicast Bits 5-6: reserved for future use Bit 7: set to 1 if IPv4 multicast",,,"See [RFC1112] for the specification of reserved IPv4 multicast addresses. See [RFC4291] for the specification of reserved IPv6 multicast addresses and the definition of the T flag and the IPv6 multicast scope. The diagram provided in [RFC5102] is incorrect. The diagram in this registry is taken from Errata 1736. See [RFC Errata 1736].",[RFC5102],0,2013-02-18 207,ipv4IHL,unsigned8,,current,"The value of the Internet Header Length (IHL) field in the IPv4 header. It specifies the length of the header in units of 4 octets. Please note that its unit is different from most of the other Information Elements reporting length values.",4-octet words,,"See [RFC791] for the specification of the IPv4 header.",[RFC5102],0,2013-02-18 208,ipv4Options,unsigned32,flags,current,"IPv4 options in packets of this Flow. The information is encoded in a set of bit fields. For each valid IPv4 option type, there is a bit in this set. The bit is set to 1 if any observed packet of this Flow contains the corresponding IPv4 option type. Otherwise, if no observed packet of this Flow contained the respective IPv4 option type, the value of the corresponding bit is 0. The list of valid IPv4 options is maintained by IANA. Note that for identifying an option not just the 5-bit Option Number, but all 8 bits of the Option Type need to match one of the IPv4 options specified at http://www.iana.org/assignments/ip-parameters. Options are mapped to bits according to their option numbers. Option number X is mapped to bit X. The mapping is illustrated by the figure below. 0 1 2 3 4 5 6 7 +------+------+------+------+------+------+------+------+ ... | RR |CIPSO |E-SEC | TS | LSR | SEC | NOP | EOOL | +------+------+------+------+------+------+------+------+ 8 9 10 11 12 13 14 15 +------+------+------+------+------+------+------+------+ ... |ENCODE| VISA | FINN | MTUR | MTUP | ZSU | SSR | SID | ... +------+------+------+------+------+------+------+------+ 16 17 18 19 20 21 22 23 +------+------+------+------+------+------+------+------+ ... | DPS |NSAPA | SDB |RTRALT|ADDEXT| TR | EIP |IMITD | ... +------+------+------+------+------+------+------+------+ 24 25 26 27 28 29 30 31 +------+------+------+------+------+------+------+------+ ... | | EXP | to be assigned by IANA | QS | UMP | +------+------+------+------+------+------+------+------+ Type Option Bit Value Name Reference ---+-----+-------+------------------------------------ 0 7 RR Record Route, RFC 791 1 134 CIPSO Commercial Security 2 133 E-SEC Extended Security, RFC 1108 3 68 TS Time Stamp, RFC 791 4 131 LSR Loose Source Route, RFC791 5 130 SEC Security, RFC 1108 6 1 NOP No Operation, RFC 791 7 0 EOOL End of Options List, RFC 791 8 15 ENCODE 9 142 VISA Experimental Access Control 10 205 FINN Experimental Flow Control 11 12 MTUR (obsoleted) MTU Reply, RFC 1191 12 11 MTUP (obsoleted) MTU Probe, RFC 1191 13 10 ZSU Experimental Measurement 14 137 SSR Strict Source Route, RFC 791 15 136 SID Stream ID, RFC 791 16 151 DPS Dynamic Packet State 17 150 NSAPA NSAP Address 18 149 SDB Selective Directed Broadcast 19 147 ADDEXT Address Extension 20 148 RTRALT Router Alert, RFC 2113 21 82 TR Traceroute, RFC 3193 22 145 EIP Extended Internet Protocol, RFC 1385 23 144 IMITD IMI Traffic Descriptor 25 30 EXP RFC3692-style Experiment 25 94 EXP RFC3692-style Experiment 25 158 EXP RFC3692-style Experiment 25 222 EXP RFC3692-style Experiment 30 25 QS Quick-Start 31 152 UMP Upstream Multicast Pkt. ... ... ... Further options numbers may be assigned by IANA",,,"See [RFC791] for the definition of IPv4 options. See the list of IPv4 option numbers assigned by IANA at [https://www.iana.org/assignments/ip-parameters]. The diagram provided in [RFC5102] is incorrect. The diagram in this registry is taken from Errata 1737. See [RFC Errata 1737] .",[RFC5102],0,2013-02-18 209,tcpOptions,unsigned64,flags,current,"TCP options in packets of this Flow. The information is encoded in a set of bit fields. For each TCP option, there is a bit in this set. The bit is set to 1 if any observed packet of this Flow contains the corresponding TCP option. Otherwise, if no observed packet of this Flow contained the respective TCP option, the value of the corresponding bit is 0. Options are mapped to bits according to their option numbers. Option number X is mapped to bit X. TCP option numbers are maintained by IANA. 0 1 2 3 4 5 6 7 +-----+-----+-----+-----+-----+-----+-----+-----+ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ... +-----+-----+-----+-----+-----+-----+-----+-----+ 8 9 10 11 12 13 14 15 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |... +-----+-----+-----+-----+-----+-----+-----+-----+ 16 17 18 19 20 21 22 23 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |... +-----+-----+-----+-----+-----+-----+-----+-----+ . . . 56 57 58 59 60 61 62 63 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | 63 | 62 | 61 | 60 | 59 | 58 | 57 | 56 | +-----+-----+-----+-----+-----+-----+-----+-----+",,,"See [RFC9293] for the definition of TCP options. See the list of TCP option numbers assigned by IANA at [https://www.iana.org/assignments/tcp-parameters]. The diagram provided in [RFC5102] is incorrect. The diagram in this registry is taken from Errata 1739. See [RFC Errata 1739].",[RFC5102],0,2013-02-18 210,paddingOctets,octetArray,default,current,"The value of this Information Element is always a sequence of 0x00 values.",,,,[RFC5102],0,2013-02-18 211,collectorIPv4Address,ipv4Address,default,current,"An IPv4 address to which the Exporting Process sends Flow information.",,,,[RFC5102],1,2014-02-03 212,collectorIPv6Address,ipv6Address,default,current,"An IPv6 address to which the Exporting Process sends Flow information.",,,,[RFC5102],1,2014-02-03 213,exportInterface,unsigned32,identifier,current,"The index of the interface from which IPFIX Messages sent by the Exporting Process to a Collector leave the IPFIX Device. The value matches the value of managed object 'ifIndex' as defined in [RFC2863]. Note that ifIndex values are not assigned statically to an interface and that the interfaces may be renumbered every time the device's management system is re-initialized, as specified in [RFC2863].",,,"See [RFC2863] for the definition of the ifIndex object.",[RFC5102],0,2013-02-18 214,exportProtocolVersion,unsigned8,identifier,current,"The protocol version used by the Exporting Process for sending Flow information. The protocol version is given by the value of the Version Number field in the Message Header. The protocol version is 10 for IPFIX and 9 for NetFlow version 9. A value of 0 indicates that no export protocol is in use.",,,"See the [IPFIX protocol specification] for the definition of the IPFIX Message Header. See [RFC3954] for the definition of the NetFlow version 9 message header.",[RFC5102],0,2013-02-18 215,exportTransportProtocol,unsigned8,identifier,current,"The value of the protocol number used by the Exporting Process for sending Flow information. The protocol number identifies the IP packet payload type. Protocol numbers are defined in the IANA Protocol Numbers registry. In Internet Protocol version 4 (IPv4), this is carried in the Protocol field. In Internet Protocol version 6 (IPv6), this is carried in the Next Header field in the last extension header of the packet.",,,"See [RFC791] for the specification of the IPv4 protocol field. See [RFC8200] for the specification of the IPv6 protocol field. See the list of protocol numbers assigned by IANA at [https://www.iana.org/assignments/protocol-numbers].",[RFC5102],0,2013-02-18 216,collectorTransportPort,unsigned16,identifier,current,"The destination port identifier to which the Exporting Process sends Flow information. For the transport protocols UDP, TCP, and SCTP, this is the destination port number. This field MAY also be used for future transport protocols that have 16-bit source port identifiers.",,,"See [RFC768] for the definition of the UDP destination port field. See [RFC9293] for the definition of the TCP destination port field. See [RFC9260] for the definition of SCTP. Additional information on defined UDP and TCP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",[RFC5102],0,2013-02-18 217,exporterTransportPort,unsigned16,identifier,current,"The source port identifier from which the Exporting Process sends Flow information. For the transport protocols UDP, TCP, and SCTP, this is the source port number. This field MAY also be used for future transport protocols that have 16-bit source port identifiers. This field may be useful for distinguishing multiple Exporting Processes that use the same IP address.",,,"See [RFC768] for the definition of the UDP source port field. See [RFC9293] for the definition of the TCP source port field. See [RFC9260] for the definition of SCTP. Additional information on defined UDP and TCP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",[RFC5102],0,2013-02-18 218,tcpSynTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Synchronize sequence numbers"" (SYN) flag set.",packets,,"See [RFC9293] for the definition of the TCP SYN flag.",[RFC5102],0,2013-02-18 219,tcpFinTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""No more data from sender"" (FIN) flag set.",packets,,"See [RFC9293] for the definition of the TCP FIN flag.",[RFC5102],0,2013-02-18 220,tcpRstTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Reset the connection"" (RST) flag set.",packets,,"See [RFC9293] for the definition of the TCP RST flag.",[RFC5102],0,2013-02-18 221,tcpPshTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Push Function"" (PSH) flag set.",packets,,"See [RFC9293] for the definition of the TCP PSH flag.",[RFC5102],0,2013-02-18 222,tcpAckTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Acknowledgment field significant"" (ACK) flag set.",packets,,"See [RFC9293] for the definition of the TCP ACK flag.",[RFC5102],0,2013-02-18 223,tcpUrgTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Urgent Pointer field significant"" (URG) flag set.",packets,,"See [RFC9293] for the definition of the TCP URG flag.",[RFC5102],0,2013-02-18 224,ipTotalLength,unsigned64,,current,The total length of the IP packet.,octets,,"See [RFC791] for the specification of the IPv4 total length. See [RFC8200] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload length.",[RFC5102],0,2013-02-18 225,postNATSourceIPv4Address,ipv4Address,default,current,"The definition of this Information Element is identical to the definition of Information Element 'sourceIPv4Address', except that it reports a modified value caused by a NAT middlebox function after the packet passed the Observation Point.",,,"See [RFC791] for the definition of the IPv4 source address field. See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.",[ipfix-iana_at_cisco.com],1,2014-02-03 226,postNATDestinationIPv4Address,ipv4Address,default,current,"The definition of this Information Element is identical to the definition of Information Element 'destinationIPv4Address', except that it reports a modified value caused by a NAT middlebox function after the packet passed the Observation Point.",,,"See [RFC791] for the definition of the IPv4 destination address field. See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.",[ipfix-iana_at_cisco.com],1,2014-02-03 227,postNAPTSourceTransportPort,unsigned16,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'sourceTransportPort', except that it reports a modified value caused by a Network Address Port Translation (NAPT) middlebox function after the packet passed the Observation Point.",,,"See [RFC768] for the definition of the UDP source port field. See [RFC9293] for the definition of the TCP source port field. See [RFC9260] for the definition of SCTP. See [RFC3022] for the definition of NAPT. See [RFC3234] for the definition of middleboxes. Additional information on defined UDP and TCP port numbers can be found at http://www.iana.org/assignments/port-numbers.",[ipfix-iana_at_cisco.com],0,2013-02-18 228,postNAPTDestinationTransportPort,unsigned16,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'destinationTransportPort', except that it reports a modified value caused by a Network Address Port Translation (NAPT) middlebox function after the packet passed the Observation Point.",,,"See [RFC768] for the definition of the UDP source port field. See [RFC9293] for the definition of the TCP source port field. See [RFC9260] for the definition of SCTP. See [RFC3022] for the definition of NAPT. See [RFC3234] for the definition of middleboxes. Additional information on defined UDP and TCP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",[ipfix-iana_at_cisco.com],0,2013-02-18 229,natOriginatingAddressRealm,unsigned8,identifier,current,"Indicates whether the session was created because traffic originated in the private or public address realm. postNATSourceIPv4Address, postNATDestinationIPv4Address, postNAPTSourceTransportPort, and postNAPTDestinationTransportPort are qualified with the address realm in perspective. Values are listed in the natOriginatingAddressRealm registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-nat-originating-address-realm].",,1-2,See [RFC3022] for the definition of NAT.,[ipfix-iana_at_cisco.com],1,2014-08-13 230,natEvent,unsigned8,identifier,current,"This Information Element identifies a NAT event. This IE identifies the type of a NAT event. Examples of NAT events include, but are not limited to, NAT translation create, NAT translation delete, Threshold Reached, or Threshold Exceeded, etc. Values for this Information Element are listed in the ""NAT Event Type"" registry, see [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-nat-event-type].",,,"See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes. See [RFC8158] for the definitions of values 4-16.",[RFC8158],2,2017-03-15 231,initiatorOctets,unsigned64,deltaCounter,current,"The total number of layer 4 payload bytes in a flow from the initiator since the previous report. The initiator is the device which triggered the session creation, and remains the same for the life of the session.",octets,,"See #298, initiatorPackets.",[ipfix-iana_at_cisco.com],1,2014-08-13 232,responderOctets,unsigned64,deltaCounter,current,"The total number of layer 4 payload bytes in a flow from the responder since the previous report. The responder is the device which replies to the initiator, and remains the same for the life of the session.",octets,,"See #299, responderPackets.",[ipfix-iana_at_cisco.com],1,2014-08-13 233,firewallEvent,unsigned8,,current,Indicates a firewall event. Allowed values are listed in the firewallEvent registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-firewall-event].,,,,[ipfix-iana_at_cisco.com],0,2013-02-18 234,ingressVRFID,unsigned32,,current,"An unique identifier of the VRFname where the packets of this flow are being received. This identifier is unique per Metering Process",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 235,egressVRFID,unsigned32,,current,"An unique identifier of the VRFname where the packets of this flow are being sent. This identifier is unique per Metering Process",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 236,VRFname,string,default,current,The name of a VPN Routing and Forwarding table (VRF).,,,See [RFC4364] for the definition of VRF.,[ipfix-iana_at_cisco.com],0,2013-02-18 237,postMplsTopLabelExp,unsigned8,flags,current,"The definition of this Information Element is identical to the definition of Information Element 'mplsTopLabelExp', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,"See [RFC3032] for the specification of the Exp field. See [RFC3270] for usage of the Exp field.",[RFC5102],0,2013-02-18 238,tcpWindowScale,unsigned16,,current,The scale of the window field in the TCP header.,,,"See [RFC1323] for the definition of the TCP window scale.",[RFC5102],0,2013-02-18 239,biflowDirection,unsigned8,identifier,current,"A description of the direction assignment method used to assign the Biflow Source and Destination. This Information Element MAY be present in a Flow Data Record, or applied to all flows exported from an Exporting Process or Observation Domain using IPFIX Options. If this Information Element is not present in a Flow Record or associated with a Biflow via scope, it is assumed that the configuration of the direction assignment method is done out-of-band. Note that when using IPFIX Options to apply this Information Element to all flows within an Observation Domain or from an Exporting Process, the Option SHOULD be sent reliably. If reliable transport is not available (i.e., when using UDP), this Information Element SHOULD appear in each Flow Record. Values are listed in the biflowDirection registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-biflow-direction].",,,,[RFC5103],0,2013-02-18 240,ethernetHeaderLength,unsigned8,quantity,current,"The difference between the length of an Ethernet frame (minus the FCS) and the length of its MAC Client Data section (including any padding) as defined in section 3.1 of [IEEE.802-3.2005]. It does not include the Preamble, SFD and Extension field lengths.",octets,,[IEEE.802-3.2005],[ipfix-iana_at_cisco.com],1,2014-08-13 241,ethernetPayloadLength,unsigned16,quantity,current,"The length of the MAC Client Data section (including any padding) of a frame as defined in section 3.1 of [IEEE.802-3.2005].",octets,,[IEEE.802-3.2005],[ipfix-iana_at_cisco.com],1,2014-08-13 242,ethernetTotalLength,unsigned16,quantity,current,"The total length of the Ethernet frame (excluding the Preamble, SFD, Extension and FCS fields) as described in section 3.1 of [IEEE.802-3.2005].",octets,,[IEEE.802-3.2005],[ipfix-iana_at_cisco.com],1,2014-08-13 243,dot1qVlanId,unsigned16,identifier,current,"The value of the 12-bit VLAN Identifier portion of the Tag Control Information field of an Ethernet frame. The structure and semantics within the Tag Control Information field are defined in [IEEE802.1Q]. In Provider Bridged Networks, it represents the Service VLAN identifier in the Service VLAN Tag (S-TAG) Tag Control Information (TCI) field or the Customer VLAN identifier in the Customer VLAN Tag (C-TAG) Tag Control Information (TCI) field as described in [IEEE802.1Q]. In Provider Backbone Bridged Networks, it represents the Backbone VLAN identifier in the Backbone VLAN Tag (B-TAG) Tag Control Information (TCI) field as described in [IEEE802.1Q]. In a virtual link between a host system and EVB bridge, it represents the Service VLAN identifier indicating S-channel as described in [IEEE802.1Qbg]. In the case of a multi-tagged frame, it represents the outer tag's VLAN identifier, except for I-TAG.",,,[IEEE802.1Q][IEEE802.1Qbg],[ipfix-iana_at_cisco.com][RFC7133],2,2014-01-11 244,dot1qPriority,unsigned8,identifier,current,"The value of the 3-bit User Priority portion of the Tag Control Information field of an Ethernet frame. The structure and semantics within the Tag Control Information field are defined in [IEEE802.1Q]. In the case of multi-tagged frame, it represents the 3-bit Priority Code Point (PCP) portion of the outer tag's Tag Control Information (TCI) field as described in [IEEE802.1Q], except for I-TAG.",,,[IEEE802.1Q],[ipfix-iana_at_cisco.com][RFC7133],1,2014-01-11 245,dot1qCustomerVlanId,unsigned16,identifier,current,"The value represents the Customer VLAN identifier in the Customer VLAN Tag (C-TAG) Tag Control Information (TCI) field as described in [IEEE802.1Q].",,,[IEEE802.1Q],[ipfix-iana_at_cisco.com][RFC7133],1,2014-01-11 246,dot1qCustomerPriority,unsigned8,identifier,current,"The value represents the 3-bit Priority Code Point (PCP) portion of the Customer VLAN Tag (C-TAG) Tag Control Information (TCI) field as described in [IEEE802.1Q].",,,[IEEE802.1Q],[ipfix-iana_at_cisco.com][RFC7133],1,2014-01-11 247,metroEvcId,string,default,current,"The EVC Service Attribute which uniquely identifies the Ethernet Virtual Connection (EVC) within a Metro Ethernet Network, as defined in section 6.2 of MEF 10.1. The MetroEVCID is encoded in a string of up to 100 characters.",,,"MEF 10.1 (Ethernet Services Attributes Phase 2) MEF16 (Ethernet Local Management Interface)",[ipfix-iana_at_cisco.com],1,2014-02-03 248,metroEvcType,unsigned8,identifier,current,"The 3-bit EVC Service Attribute which identifies the type of service provided by an EVC.",,,"MEF 10.1 (Ethernet Services Attributes Phase 2) MEF16 (Ethernet Local Management Interface)",[ipfix-iana_at_cisco.com],0,2013-02-18 249,pseudoWireId,unsigned32,identifier,current,"A 32-bit non-zero connection identifier, which together with the pseudoWireType, identifies the Pseudo Wire (PW) as defined in [RFC8077].",,,See [RFC8077] for pseudowire definitions.,[ipfix-iana_at_cisco.com],0,2013-02-18 250,pseudoWireType,unsigned16,identifier,current,"The value of this information element identifies the type of MPLS Pseudo Wire (PW) as defined in [RFC4446].",,,"See [RFC4446] for the pseudowire type definition, and [https://www.iana.org/assignments/pwe3-parameters] for the IANA Pseudowire Types registry.",[ipfix-iana_at_cisco.com],0,2013-02-18 251,pseudoWireControlWord,unsigned32,identifier,current,"The 32-bit Preferred Pseudo Wire (PW) MPLS Control Word as defined in Section 3 of [RFC4385].",,,"See [RFC4385] for the Pseudo Wire Control Word definition.",[ipfix-iana_at_cisco.com],0,2013-02-18 252,ingressPhysicalInterface,unsigned32,identifier,current,"The index of a networking device's physical interface (example, a switch port) where packets of this flow are being received.",,,See [RFC2863] for the definition of the ifIndex object.,[ipfix-iana_at_cisco.com],0,2013-02-18 253,egressPhysicalInterface,unsigned32,identifier,current,"The index of a networking device's physical interface (example, a switch port) where packets of this flow are being sent.",,,See [RFC2863] for the definition of the ifIndex object.,[ipfix-iana_at_cisco.com],0,2013-02-18 254,postDot1qVlanId,unsigned16,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'dot1qVlanId', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,"[IEEE.802-3.2005] [IEEE.802-1ad.2005]",[ipfix-iana_at_cisco.com],0,2013-02-18 255,postDot1qCustomerVlanId,unsigned16,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'dot1qCustomerVlanId', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,"[IEEE.802-1ad.2005] [IEEE.802-1Q.2003]",[ipfix-iana_at_cisco.com],0,2013-02-18 256,ethernetType,unsigned16,identifier,current,"The Ethernet type field of an Ethernet frame that identifies the MAC client protocol carried in the payload as defined in paragraph 1.4.349 of [IEEE.802-3.2005].",,,"[IEEE.802-3.2005] Ethertype registry available at [http://standards.ieee.org/regauth/ethertype/eth.txt]",[ipfix-iana_at_cisco.com],0,2013-02-18 257,postIpPrecedence,unsigned8,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'ipPrecedence', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,0-7,"See [RFC1812] (Section 5.3.3) and [RFC791] for the definition of the IP Precedence. See [RFC1812] (Section 5.3.2) and [RFC791] for the definition of the IPv4 TOS field. See [RFC8200] for the definition of the IPv6 Traffic Class field.",[ipfix-iana_at_cisco.com],0,2013-02-18 258,collectionTimeMilliseconds,dateTimeMilliseconds,default,current,"The absolute timestamp at which the data within the scope containing this Information Element was received by a Collecting Process. This Information Element SHOULD be bound to its containing IPFIX Message via IPFIX Options and the messageScope Information Element, as defined below.",milliseconds,,,[RFC5655][RFC Errata 3559],1,2013-03-26 259,exportSctpStreamId,unsigned16,identifier,current,"The value of the SCTP Stream Identifier used by the Exporting Process for exporting IPFIX Message data. This is carried in the Stream Identifier field of the header of the SCTP DATA chunk containing the IPFIX Message(s).",,,,[RFC5655],0,2013-02-18 260,maxExportSeconds,dateTimeSeconds,default,current,"The absolute Export Time of the latest IPFIX Message within the scope containing this Information Element. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element.",seconds,,,[RFC5655],0,2013-02-18 261,maxFlowEndSeconds,dateTimeSeconds,default,current,"The latest absolute timestamp of the last packet within any Flow within the scope containing this Information Element, rounded up to the second if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element.",seconds,,,[RFC5655],0,2013-02-18 262,messageMD5Checksum,octetArray,default,current,"The MD5 checksum of the IPFIX Message containing this record. This Information Element SHOULD be bound to its containing IPFIX Message via an options record and the messageScope Information Element, as defined below, and SHOULD appear only once in a given IPFIX Message. To calculate the value of this Information Element, first buffer the containing IPFIX Message, setting the value of this Information Element to all zeroes. Then calculate the MD5 checksum of the resulting buffer as defined in [RFC1321], place the resulting value in this Information Element, and export the buffered message. This Information Element is intended as a simple checksum only; therefore collision resistance and algorithm agility are not required, and MD5 is an appropriate message digest. This Information Element has a fixed length of 16 octets.",,,,[RFC5655][RFC1321],0,2013-02-18 263,messageScope,unsigned8,,current,"The presence of this Information Element as scope in an Options Template signifies that the options described by the Template apply to the IPFIX Message that contains them. It is defined for general purpose message scoping of options, and proposed specifically to allow the attachment a checksum to a message via IPFIX Options. The value of this Information Element MUST be written as 0 by the File Writer or Exporting Process. The value of this Information Element MUST be ignored by the File Reader or the Collecting Process.",,0-0,,[RFC5655],0,2013-02-18 264,minExportSeconds,dateTimeSeconds,default,current,"The absolute Export Time of the earliest IPFIX Message within the scope containing this Information Element. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element.",seconds,,,[RFC5655],0,2013-02-18 265,minFlowStartSeconds,dateTimeSeconds,default,current,"The earliest absolute timestamp of the first packet within any Flow within the scope containing this Information Element, rounded down to the second if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element.",seconds,,,[RFC5655],0,2013-02-18 266,opaqueOctets,octetArray,default,current,"This Information Element is used to encapsulate non- IPFIX data into an IPFIX Message stream, for the purpose of allowing a non-IPFIX data processor to store a data stream inline within an IPFIX File. A Collecting Process or File Writer MUST NOT try to interpret this binary data. This Information Element differs from paddingOctets as its contents are meaningful in some non-IPFIX context, while the contents of paddingOctets MUST be 0x00 and are intended only for Information Element alignment.",,,,[RFC5655],0,2013-02-18 267,sessionScope,unsigned8,,current,"The presence of this Information Element as scope in an Options Template signifies that the options described by the Template apply to the IPFIX Transport Session that contains them. Note that as all options are implicitly scoped to Transport Session and Observation Domain, this Information Element is equivalent to a ""null"" scope. It is defined for general purpose session scoping of options, and proposed specifically to allow the attachment of time window to an IPFIX File via IPFIX Options. The value of this Information Element MUST be written as 0 by the File Writer or Exporting Process. The value of this Information Element MUST be ignored by the File Reader or the Collecting Process.",,0-0,,[RFC5655],0,2013-02-18 268,maxFlowEndMicroseconds,dateTimeMicroseconds,default,current,"The latest absolute timestamp of the last packet within any Flow within the scope containing this Information Element, rounded up to the microsecond if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with microsecond- precision (or better) timestamp Information Elements.",microseconds,,,[RFC5655],0,2013-02-18 269,maxFlowEndMilliseconds,dateTimeMilliseconds,default,current,"The latest absolute timestamp of the last packet within any Flow within the scope containing this Information Element, rounded up to the millisecond if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with millisecond- precision (or better) timestamp Information Elements.",milliseconds,,,[RFC5655],0,2013-02-18 270,maxFlowEndNanoseconds,dateTimeNanoseconds,default,current,"The latest absolute timestamp of the last packet within any Flow within the scope containing this Information Element. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with nanosecond-precision timestamp Information Elements.",nanoseconds,,,[RFC5655],0,2013-02-18 271,minFlowStartMicroseconds,dateTimeMicroseconds,default,current,"The earliest absolute timestamp of the first packet within any Flow within the scope containing this Information Element, rounded down to the microsecond if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with microsecond- precision (or better) timestamp Information Elements.",microseconds,,,[RFC5655],0,2013-02-18 272,minFlowStartMilliseconds,dateTimeMilliseconds,default,current,"The earliest absolute timestamp of the first packet within any Flow within the scope containing this Information Element, rounded down to the millisecond if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with millisecond- precision (or better) timestamp Information Elements.",milliseconds,,,[RFC5655],0,2013-02-18 273,minFlowStartNanoseconds,dateTimeNanoseconds,default,current,"The earliest absolute timestamp of the first packet within any Flow within the scope containing this Information Element. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with nanosecond-precision timestamp Information Elements.",nanoseconds,,,[RFC5655],0,2013-02-18 274,collectorCertificate,octetArray,default,current,"The full X.509 certificate, encoded in ASN.1 DER format, used by the Collector when IPFIX Messages were transmitted using TLS or DTLS. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element, or to its containing IPFIX Message via an options record and the messageScope Information Element.",,,,[RFC5655],0,2013-02-18 275,exporterCertificate,octetArray,default,current,"The full X.509 certificate, encoded in ASN.1 DER format, used by the Collector when IPFIX Messages were transmitted using TLS or DTLS. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element, or to its containing IPFIX Message via an options record and the messageScope Information Element.",,,,[RFC5655],0,2013-02-18 276,dataRecordsReliability,boolean,default,current,"The export reliability of Data Records, within this SCTP stream, for the element(s) in the Options Template scope. A typical example of an element for which the export reliability will be reported is the templateID, as specified in the Data Records Reliability Options Template. A value of 'True' means that the Exporting Process MUST send any Data Records associated with the element(s) reliably within this SCTP stream. A value of 'False' means that the Exporting Process MAY send any Data Records associated with the element(s) unreliably within this SCTP stream.",,,,[RFC6526],1,2014-02-03 277,observationPointType,unsigned8,identifier,current,Type of observation point. Values are listed in the observationPointType registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-observation-point-type].,,,,[ipfix-iana_at_cisco.com],0,2013-02-18 278,newConnectionDeltaCount,unsigned32,deltaCounter,current,"This information element counts the number of TCP or UDP connections which were opened during the observation period. The observation period may be specified by the flow start and end timestamps.",,,,[ipfix-iana_at_cisco.com],1,2014-08-13 279,connectionSumDurationSeconds,unsigned64,,current,"This information element aggregates the total time in seconds for all of the TCP or UDP connections which were in use during the observation period. For example if there are 5 concurrent connections each for 10 seconds, the value would be 50 s.",seconds,,,[ipfix-iana_at_cisco.com],1,2013-06-25 280,connectionTransactionId,unsigned64,identifier,current,"This information element identifies a transaction within a connection. A transaction is a meaningful exchange of application data between two network devices or a client and server. A transactionId is assigned the first time a flow is reported, so that later reports for the same flow will have the same transactionId. A different transactionId is used for each transaction within a TCP or UDP connection. The identifiers need not be sequential.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 281,postNATSourceIPv6Address,ipv6Address,default,current,"The definition of this Information Element is identical to the definition of Information Element 'sourceIPv6Address', except that it reports a modified value caused by a NAT64 middlebox function after the packet passed the Observation Point. See [RFC8200] for the definition of the Source Address field in the IPv6 header. See [RFC3234] for the definition of middleboxes. See [RFC6146] for nat64 specification.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 282,postNATDestinationIPv6Address,ipv6Address,default,current,"The definition of this Information Element is identical to the definition of Information Element 'destinationIPv6Address', except that it reports a modified value caused by a NAT64 middlebox function after the packet passed the Observation Point. See [RFC8200] for the definition of the Destination Address field in the IPv6 header. See [RFC3234] for the definition of middleboxes. See [RFC6146] for nat64 specification.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 283,natPoolId,unsigned32,identifier,current,Locally unique identifier of a NAT pool.,,,,[ipfix-iana_at_cisco.com],0,2013-02-18 284,natPoolName,string,default,current,The name of a NAT pool identified by a natPoolID.,,,,[ipfix-iana_at_cisco.com],0,2013-02-18 285,anonymizationFlags,unsigned16,flags,current,"A flag word describing specialized modifications to the anonymization policy in effect for the anonymization technique applied to a referenced Information Element within a referenced Template. When flags are clear (0), the normal policy (as described by anonymizationTechnique) applies without modification. MSB 14 13 12 11 10 9 8 7 6 5 4 3 2 1 LSB +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | Reserved |LOR|PmA| SC | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ anonymizationFlags IE +--------+----------+-----------------------------------------------+ | bit(s) | name | description | | (LSB = | | | | 0) | | | +--------+----------+-----------------------------------------------+ | 0-1 | SC | Stability Class: see the Stability Class | | | | table below, and section Section 5.1. | | 2 | PmA | Perimeter Anonymization: when set (1), | | | | source- Information Elements as described in | | | | [RFC5103] are interpreted as external | | | | addresses, and destination- Information | | | | Elements as described in [RFC5103] are | | | | interpreted as internal addresses, for the | | | | purposes of associating | | | | anonymizationTechnique to Information | | | | Elements only; see Section 7.2.2 for details. | | | | This bit MUST NOT be set when associated with | | | | a non-endpoint (i.e., source- or | | | | destination-) Information Element. SHOULD be | | | | consistent within a record (i.e., if a | | | | source- Information Element has this flag | | | | set, the corresponding destination- element | | | | SHOULD have this flag set, and vice-versa.) | | 3 | LOR | Low-Order Unchanged: when set (1), the | | | | low-order bits of the anonymized Information | | | | Element contain real data. This modification | | | | is intended for the anonymization of | | | | network-level addresses while leaving | | | | host-level addresses intact in order to | | | | preserve host level-structure, which could | | | | otherwise be used to reverse anonymization. | | | | MUST NOT be set when associated with a | | | | truncation-based anonymizationTechnique. | | 4-15 | Reserved | Reserved for future use: SHOULD be cleared | | | | (0) by the Exporting Process and MUST be | | | | ignored by the Collecting Process. | +--------+----------+-----------------------------------------------+ The Stability Class portion of this flags word describes the stability class of the anonymization technique applied to a referenced Information Element within a referenced Template. Stability classes refer to the stability of the parameters of the anonymization technique, and therefore the comparability of the mapping between the real and anonymized values over time. This determines which anonymized datasets may be compared with each other. Values are as follows: +-----+-----+-------------------------------------------------------+ | Bit | Bit | Description | | 1 | 0 | | +-----+-----+-------------------------------------------------------+ | 0 | 0 | Undefined: the Exporting Process makes no | | | | representation as to how stable the mapping is, or | | | | over what time period values of this field will | | | | remain comparable; while the Collecting Process MAY | | | | assume Session level stability, Session level | | | | stability is not guaranteed. Processes SHOULD assume | | | | this is the case in the absence of stability class | | | | information; this is the default stability class. | | 0 | 1 | Session: the Exporting Process will ensure that the | | | | parameters of the anonymization technique are stable | | | | during the Transport Session. All the values of the | | | | described Information Element for each Record | | | | described by the referenced Template within the | | | | Transport Session are comparable. The Exporting | | | | Process SHOULD endeavour to ensure at least this | | | | stability class. | | 1 | 0 | Exporter-Collector Pair: the Exporting Process will | | | | ensure that the parameters of the anonymization | | | | technique are stable across Transport Sessions over | | | | time with the given Collecting Process, but may use | | | | different parameters for different Collecting | | | | Processes. Data exported to different Collecting | | | | Processes are not comparable. | | 1 | 1 | Stable: the Exporting Process will ensure that the | | | | parameters of the anonymization technique are stable | | | | across Transport Sessions over time, regardless of | | | | the Collecting Process to which it is sent. | +-----+-----+-------------------------------------------------------+",,,,[RFC6235],0,2013-02-18 286,anonymizationTechnique,unsigned16,identifier,current,"A description of the anonymization technique applied to a referenced Information Element within a referenced Template. Each technique may be applicable only to certain Information Elements and recommended only for certain Information Elements. Values are listed in the anonymizationTechnique registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-anonymization-technique].",,,,[RFC6235],0,2013-02-18 287,informationElementIndex,unsigned16,identifier,current,"A zero-based index of an Information Element referenced by informationElementId within a Template referenced by templateId; used to disambiguate scope for templates containing multiple identical Information Elements.",,,,[RFC6235],0,2013-02-18 288,p2pTechnology,string,default,current,"Specifies if the Application ID is based on peer-to-peer technology. Possible values are: { ""yes"", ""y"", 1 }, { ""no"", ""n"", 2 } and { ""unassigned"", ""u"", 0 }.",,,,[RFC6759],0,2013-02-18 289,tunnelTechnology,string,default,current,"Specifies if the Application ID is used as a tunnel technology. Possible values are: { ""yes"", ""y"", 1 }, { ""no"", ""n"", 2 } and { ""unassigned"", ""u"", 0 }.",,,,[RFC6759],0,2013-02-18 290,encryptedTechnology,string,default,current,"Specifies if the Application ID is an encrypted networking protocol. Possible values are: { ""yes"", ""y"", 1 }, { ""no"", ""n"", 2 } and { ""unassigned"", ""u"", 0 }.",,,,[RFC6759],0,2013-02-18 291,basicList,basicList,list,current,"Specifies a generic Information Element with a basicList abstract data type. For example, a list of port numbers, a list of interface indexes, etc.",,,,[RFC6313],0,2013-02-18 292,subTemplateList,subTemplateList,list,current,"Specifies a generic Information Element with a subTemplateList abstract data type.",,,,[RFC6313],0,2013-02-18 293,subTemplateMultiList,subTemplateMultiList,list,current,"Specifies a generic Information Element with a subTemplateMultiList abstract data type.",,,,[RFC6313],0,2013-02-18 294,bgpValidityState,unsigned8,identifier,current,"This element describes the ""validity state"" of the BGP route correspondent source or destination IP address. If the ""validity state"" for this Flow is only available, then the value of this Information Element is 255.",,,"See [RFC4271] for a description of BGP-4, [RFC6811] for the definition of ""validity states"" and [draft-ietf-sidr-origin-validation-signaling] for the encoding of those ""validity states"".",[ipfix-iana_at_cisco.com],0,2013-02-18 295,IPSecSPI,unsigned32,identifier,current,IPSec Security Parameters Index (SPI).,,,See [RFC2401] for the definition of SPI.,[ipfix-iana_at_cisco.com],0,2013-02-18 296,greKey,unsigned32,identifier,current,"GRE key, which is used for identifying an individual traffic flow within a tunnel.",,,See [RFC1701] for the definition of GRE and the GRE Key.,[ipfix-iana_at_cisco.com],0,2013-02-18 297,natType,unsigned8,identifier,current,Values are listed in the natType registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-nat-type].,,,"See [RFC3022] for the definition of NAT. See [RFC1631] for the definition of NAT44. See [RFC6144] for the definition of NAT64. See [RFC6146] for the definition of NAT46. See [RFC6296] for the definition of NAT66. See [RFC791] for the definition of IPv4. See [RFC8200] for the definition of IPv6.",[ipfix-iana_at_cisco.com],0,2013-02-18 298,initiatorPackets,unsigned64,deltaCounter,current,"The total number of layer 4 packets in a flow from the initiator since the previous report. The initiator is the device which triggered the session creation, and remains the same for the life of the session.",packets,,"See #231, initiatorOctets.",[ipfix-iana_at_cisco.com],1,2014-08-13 299,responderPackets,unsigned64,deltaCounter,current,"The total number of layer 4 packets in a flow from the responder since the previous report. The responder is the device which replies to the initiator, and remains the same for the life of the session.",packets,,"See #232, responderOctets.",[ipfix-iana_at_cisco.com],1,2014-08-13 300,observationDomainName,string,default,current,"The name of an observation domain identified by an observationDomainId.",,,"See #149, observationDomainId.",[ipfix-iana_at_cisco.com],0,2013-02-18 301,selectionSequenceId,unsigned64,identifier,current,"From all the packets observed at an Observation Point, a subset of the packets is selected by a sequence of one or more Selectors. The selectionSequenceId is a unique value per Observation Domain, specifying the Observation Point and the sequence of Selectors through which the packets are selected.",,,,[RFC5477],0,2013-02-18 302,selectorId,unsigned64,identifier,current,"The Selector ID is the unique ID identifying a Primitive Selector. Each Primitive Selector must have a unique ID in the Observation Domain.",,,,[RFC5477][RFC Errata 2052],0,2013-02-18 303,informationElementId,unsigned16,identifier,current,"This Information Element contains the ID of another Information Element.",,,,[RFC5477],0,2013-02-18 304,selectorAlgorithm,unsigned16,identifier,current,"This Information Element identifies the packet selection methods (e.g., Filtering, Sampling) that are applied by the Selection Process. Most of these methods have parameters. Further Information Elements are needed to fully specify packet selection with these methods and all their parameters. The methods listed below are defined in [RFC5475]. For their parameters, Information Elements are defined in the information model document. The names of these Information Elements are listed for each method identifier. Further method identifiers may be added to the list below. It might be necessary to define new Information Elements to specify their parameters. The following packet selection methods identifiers are defined here: [https://www.iana.org/assignments/psamp-parameters] There is a broad variety of possible parameters that could be used for Property match Filtering (5) but currently there are no agreed parameters specified.",,,,[RFC5477],0,2013-02-18 305,samplingPacketInterval,unsigned32,quantity,current,"This Information Element specifies the number of packets that are consecutively sampled. A value of 100 means that 100 consecutive packets are sampled. For example, this Information Element may be used to describe the configuration of a systematic count-based Sampling Selector.",packets,,,[RFC5477],0,2013-02-18 306,samplingPacketSpace,unsigned32,quantity,current,"This Information Element specifies the number of packets between two ""samplingPacketInterval""s. A value of 100 means that the next interval starts 100 packets (which are not sampled) after the current ""samplingPacketInterval"" is over. For example, this Information Element may be used to describe the configuration of a systematic count-based Sampling Selector.",packets,,,[RFC5477],0,2013-02-18 307,samplingTimeInterval,unsigned32,quantity,current,"This Information Element specifies the time interval in microseconds during which all arriving packets are sampled. For example, this Information Element may be used to describe the configuration of a systematic time-based Sampling Selector.",microseconds,,,[RFC5477],0,2013-02-18 308,samplingTimeSpace,unsigned32,quantity,current,"This Information Element specifies the time interval in microseconds between two ""samplingTimeInterval""s. A value of 100 means that the next interval starts 100 microseconds (during which no packets are sampled) after the current ""samplingTimeInterval"" is over. For example, this Information Element may used to describe the configuration of a systematic time-based Sampling Selector.",microseconds,,,[RFC5477],0,2013-02-18 309,samplingSize,unsigned32,quantity,current,"This Information Element specifies the number of elements taken from the parent Population for random Sampling methods. For example, this Information Element may be used to describe the configuration of a random n-out-of-N Sampling Selector.",packets,,,[RFC5477],0,2013-02-18 310,samplingPopulation,unsigned32,quantity,current,"This Information Element specifies the number of elements in the parent Population for random Sampling methods. For example, this Information Element may be used to describe the configuration of a random n-out-of-N Sampling Selector.",packets,,,[RFC5477],0,2013-02-18 311,samplingProbability,float64,quantity,current,"This Information Element specifies the probability that a packet is sampled, expressed as a value between 0 and 1. The probability is equal for every packet. A value of 0 means no packet was sampled since the probability is 0. For example, this Information Element may be used to describe the configuration of a uniform probabilistic Sampling Selector.",,,,[RFC5477],0,2013-02-18 312,dataLinkFrameSize,unsigned16,quantity,current,"This Information Element specifies the length of the selected data link frame. The data link layer is defined in [ISO/IEC.7498-1:1994].",,,[ISO/IEC.7498-1:1994],[RFC7133],1,2014-01-11 313,ipHeaderPacketSection,octetArray,default,current,"This Information Element carries a series of n octets from the IP header of a sampled packet, starting sectionOffset octets into the IP header. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the start of the IP header. With sufficient length, this element also reports octets from the IP payload. However, full packet capture of arbitrary packet streams is explicitly out of scope per the Security Considerations sections of [RFC5477] and [RFC2804]. The sectionExportedOctets expresses how much data was exported, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or it MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol.",,,"[RFC2804] [RFC5477]",[RFC5477][RFC7133],1,2014-01-11 314,ipPayloadPacketSection,octetArray,default,current,"This Information Element carries a series of n octets from the IP payload of a sampled packet, starting sectionOffset octets into the IP payload. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the start of the IP payload. The IPv4 payload is that part of the packet that follows the IPv4 header and any options, which [RFC791] refers to as ""data"" or ""data octets"". For example, see the examples in [RFC791], Appendix A. The IPv6 payload is the rest of the packet following the 40-octet IPv6 header. Note that any extension headers present are considered part of the payload. See [RFC8200] for the IPv6 specification. The sectionExportedOctets expresses how much data was observed, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol.",,,"[RFC791] [RFC8200]",[RFC5477][RFC7133],1,2014-01-11 315,dataLinkFrameSection,octetArray,default,current,"This Information Element carries n octets from the data link frame of a selected frame, starting sectionOffset octets into the frame. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the start of the data link frame. The sectionExportedOctets expresses how much data was observed, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol. Further Information Elements, i.e., dataLinkFrameType and dataLinkFrameSize, are needed to specify the data link type and the size of the data link frame of this Information Element. A set of these Information Elements MAY be contained in a structured data type, as expressed in [RFC6313]. Or a set of these Information Elements MAY be contained in one Flow Record as shown in Appendix B of [RFC7133]. The data link layer is defined in [ISO/IEC.7498-1:1994].",,,"[RFC6313] [RFC7133] [ISO/IEC.7498-1:1994]",[RFC7133],1,2014-01-11 316,mplsLabelStackSection,octetArray,default,current,"This Information Element carries a series of n octets from the MPLS label stack of a sampled packet, starting sectionOffset octets into the MPLS label stack. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the head of the MPLS label stack. With sufficient length, this element also reports octets from the MPLS payload. However, full packet capture of arbitrary packet streams is explicitly out of scope per the Security Considerations sections of [RFC5477] and [RFC2804]. See [RFC3031] for the specification of MPLS packets. See [RFC3032] for the specification of the MPLS label stack. The sectionExportedOctets expresses how much data was observed, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol.",,,"[RFC2804] [RFC3031] [RFC3032] [RFC5477]",[RFC5477][RFC7133],1,2014-01-11 317,mplsPayloadPacketSection,octetArray,default,current,"The mplsPayloadPacketSection carries a series of n octets from the MPLS payload of a sampled packet, starting sectionOffset octets into the MPLS payload, as it is data that follows immediately after the MPLS label stack. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the start of the MPLS payload. See [RFC3031] for the specification of MPLS packets. See [RFC3032] for the specification of the MPLS label stack. The sectionExportedOctets expresses how much data was observed, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or it MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol.",,,"[RFC3031] [RFC3032]",[RFC5477][RFC7133],1,2014-01-11 318,selectorIdTotalPktsObserved,unsigned64,totalCounter,current,"This Information Element specifies the total number of packets observed by a Selector, for a specific value of SelectorId. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",packets,,,[RFC5477],0,2013-02-18 319,selectorIdTotalPktsSelected,unsigned64,totalCounter,current,"This Information Element specifies the total number of packets selected by a Selector, for a specific value of SelectorId. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",packets,,,[RFC5477],0,2013-02-18 320,absoluteError,float64,quantity,current,"This Information Element specifies the maximum possible measurement error of the reported value for a given Information Element. The absoluteError has the same unit as the Information Element with which it is associated. The real value of the metric can differ by absoluteError (positive or negative) from the measured value. This Information Element provides only the error for measured values. If an Information Element contains an estimated value (from Sampling), the confidence boundaries and confidence level have to be provided instead, using the upperCILimit, lowerCILimit, and confidenceLevel Information Elements. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",inferred,,,[RFC5477],1,2018-06-13 321,relativeError,float64,quantity,current,"This Information Element specifies the maximum possible positive or negative error ratio for the reported value for a given Information Element as percentage of the measured value. The real value of the metric can differ by relativeError percent (positive or negative) from the measured value. This Information Element provides only the error for measured values. If an Information Element contains an estimated value (from Sampling), the confidence boundaries and confidence level have to be provided instead, using the upperCILimit, lowerCILimit, and confidenceLevel Information Elements. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",,,,[RFC5477],0,2013-02-18 322,observationTimeSeconds,dateTimeSeconds,default,current,"This Information Element specifies the absolute time in seconds of an observation.",seconds,,,[RFC5477],1,2014-02-03 323,observationTimeMilliseconds,dateTimeMilliseconds,default,current,"This Information Element specifies the absolute time in milliseconds of an observation.",milliseconds,,,[RFC5477],1,2014-02-03 324,observationTimeMicroseconds,dateTimeMicroseconds,default,current,"This Information Element specifies the absolute time in microseconds of an observation.",microseconds,,,[RFC5477],1,2014-02-03 325,observationTimeNanoseconds,dateTimeNanoseconds,default,current,"This Information Element specifies the absolute time in nanoseconds of an observation.",nanoseconds,,,[RFC5477],1,2014-02-03 326,digestHashValue,unsigned64,quantity,current,"This Information Element specifies the value from the digest hash function. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 327,hashIPPayloadOffset,unsigned64,quantity,current,"This Information Element specifies the IP payload offset used by a Hash-based Selection Selector. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 328,hashIPPayloadSize,unsigned64,quantity,current,"This Information Element specifies the IP payload size used by a Hash-based Selection Selector. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 329,hashOutputRangeMin,unsigned64,quantity,current,"This Information Element specifies the value for the beginning of a hash function's potential output range. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 330,hashOutputRangeMax,unsigned64,quantity,current,"This Information Element specifies the value for the end of a hash function's potential output range. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 331,hashSelectedRangeMin,unsigned64,quantity,current,"This Information Element specifies the value for the beginning of a hash function's selected range. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 332,hashSelectedRangeMax,unsigned64,quantity,current,"This Information Element specifies the value for the end of a hash function's selected range. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 333,hashDigestOutput,boolean,default,current,"This Information Element contains a boolean value that is TRUE if the output from this hash Selector has been configured to be included in the packet report as a packet digest, else FALSE. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],1,2014-02-03 334,hashInitialiserValue,unsigned64,quantity,current,"This Information Element specifies the initialiser value to the hash function. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 335,selectorName,string,default,current,"The name of a selector identified by a selectorID. Globally unique per Metering Process.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 336,upperCILimit,float64,quantity,current,"This Information Element specifies the upper limit of a confidence interval. It is used to provide an accuracy statement for an estimated value. The confidence limits define the range in which the real value is assumed to be with a certain probability p. Confidence limits always need to be associated with a confidence level that defines this probability p. Please note that a confidence interval only provides a probability that the real value lies within the limits. That means the real value can lie outside the confidence limits. The upperCILimit, lowerCILimit, and confidenceLevel Information Elements should all be used in an Options Template scoped to the observation to which they refer. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011]. Note that the upperCILimit, lowerCILimit, and confidenceLevel are all required to specify confidence, and should be disregarded unless all three are specified together.",,,,[RFC5477],0,2013-02-18 337,lowerCILimit,float64,quantity,current,"This Information Element specifies the lower limit of a confidence interval. For further information, see the description of upperCILimit. The upperCILimit, lowerCILimit, and confidenceLevel Information Elements should all be used in an Options Template scoped to the observation to which they refer. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011]. Note that the upperCILimit, lowerCILimit, and confidenceLevel are all required to specify confidence, and should be disregarded unless all three are specified together.",,,,[RFC5477],0,2013-02-18 338,confidenceLevel,float64,quantity,current,"This Information Element specifies the confidence level. It is used to provide an accuracy statement for estimated values. The confidence level provides the probability p with which the real value lies within a given range. A confidence level always needs to be associated with confidence limits that define the range in which the real value is assumed to be. The upperCILimit, lowerCILimit, and confidenceLevel Information Elements should all be used in an Options Template scoped to the observation to which they refer. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011]. Note that the upperCILimit, lowerCILimit, and confidenceLevel are all required to specify confidence, and should be disregarded unless all three are specified together.",,,,[RFC5477],0,2013-02-18 339,informationElementDataType,unsigned8,,current,"A description of the abstract data type of an IPFIX information element.These are taken from the abstract data types defined in section 3.1 of the IPFIX Information Model [RFC5102]; see that section for more information on the types described in the [informationElementDataType] subregistry. These types are registered in the IANA IPFIX Information Element Data Type subregistry. This subregistry is intended to assign numbers for type names, not to provide a mechanism for adding data types to the IPFIX Protocol, and as such requires a Standards Action [RFC8126] to modify.",,,,[RFC5610],0,2013-02-18 340,informationElementDescription,string,default,current,"A UTF-8 [RFC3629] encoded Unicode string containing a human-readable description of an Information Element. The content of the informationElementDescription MAY be annotated with one or more language tags [RFC4646], encoded in-line [RFC2482] within the UTF-8 string, in order to specify the language in which the description is written. Description text in multiple languages MAY tag each section with its own language tag; in this case, the description information in each language SHOULD have equivalent meaning. In the absence of any language tag, the ""i-default"" [RFC2277] language SHOULD be assumed. See the Security Considerations section for notes on string handling for Information Element type records.",,,,[RFC5610],0,2013-02-18 341,informationElementName,string,default,current,"A UTF-8 [RFC3629] encoded Unicode string containing the name of an Information Element, intended as a simple identifier. See the Security Considerations section for notes on string handling for Information Element type records",,,,[RFC5610],0,2013-02-18 342,informationElementRangeBegin,unsigned64,quantity,current,"Contains the inclusive low end of the range of acceptable values for an Information Element.",,,,[RFC5610],0,2013-02-18 343,informationElementRangeEnd,unsigned64,quantity,current,"Contains the inclusive high end of the range of acceptable values for an Information Element.",,,,[RFC5610],0,2013-02-18 344,informationElementSemantics,unsigned8,,current,"A description of the semantics of an IPFIX Information Element. These are taken from the data type semantics defined in section 3.2 of the IPFIX Information Model [RFC5102]; see that section for more information on the types defined in the [IPFIX Information Element Semantics] subregistry. This field may take the values in the semantics registry; the special value 0x00 (default) is used to note that no semantics apply to the field; it cannot be manipulated by a Collecting Process or File Reader that does not understand it a priori. These semantics are registered in the IANA IPFIX Information Element Semantics subregistry. This subregistry is intended to assign numbers for semantics names, not to provide a mechanism for adding semantics to the IPFIX Protocol, and as such requires a Standards Action [RFC8126] to modify.",,,,[RFC5610],0,2013-02-18 345,informationElementUnits,unsigned16,,current,"A description of the units of an IPFIX Information Element. These correspond to the units implicitly defined in the Information Element definitions in section 5 of the IPFIX Information Model [RFC5102]; see that section for more information on the types described in the informationElementsUnits subregistry. This field may take the values in Table 3 below; the special value 0x00 (none) is used to note that the field is unitless. These types are registered in the [IANA IPFIX Information Element Units] subregistry.",,,,[RFC5610][RFC Errata 1822],1,2020-10-01 346,privateEnterpriseNumber,unsigned32,identifier,current,"A private enterprise number, as assigned by IANA. Within the context of an Information Element Type record, this element can be used along with the informationElementId element to scope properties to a specific Information Element. To export type information about an IANA-assigned Information Element, set the privateEnterpriseNumber to 0, or do not export the privateEnterpriseNumber in the type record. To export type information about an enterprise-specific Information Element, export the enterprise number in privateEnterpriseNumber, and export the Information Element number with the Enterprise bit cleared in informationElementId. The Enterprise bit in the associated informationElementId Information Element MUST be ignored by the Collecting Process.",,,,[RFC5610],0,2013-02-18 347,virtualStationInterfaceId,octetArray,default,current,"Instance Identifier of the interface to a Virtual Station. A Virtual Station is an end station instance: it can be a virtual machine or a physical host.",,,See IEEE 802.1Qbg for the definition of Virtual Station Interface ID.,[ipfix-iana_at_cisco.com],1,2014-02-03 348,virtualStationInterfaceName,string,default,current,"Name of the interface to a Virtual Station. A Virtual Station is an end station instance: it can be a virtual machine or a physical host.",,,See IEEE 802.1Qbg for the definition of Virtual Station Interface.,[ipfix-iana_at_cisco.com],1,2014-02-03 349,virtualStationUUID,octetArray,default,current,"Unique Identifier of a Virtual Station. A Virtual Station is an end station instance: it can be a virtual machine or a physical host.",,,See IEEE 802.1Qbg for the definition of Virtual Station.,[ipfix-iana_at_cisco.com],1,2014-02-03 350,virtualStationName,string,default,current,"Name of a Virtual Station. A Virtual Station is an end station instance: it can be a virtual machine or a physical host.",,,See IEEE 802.1Qbg for the definition of Virtual Station.,[ipfix-iana_at_cisco.com],0,2013-02-18 351,layer2SegmentId,unsigned64,identifier,current,"Identifier of a layer 2 network segment in an overlay network. The most significant byte identifies the layer 2 network overlay network encapsulation type: 0x00 reserved 0x01 VxLAN 0x02 NVGRE The three lowest significant bytes hold the value of the layer 2 overlay network segment identifier. For example: - a 24 bit segment ID VXLAN Network Identifier (VNI) - a 24 bit Tenant Network Identifier (TNI) for NVGRE",,,See VxLAN RFC at [RFC7348] See NVGRE RFC at [RFC7637],[ipfix-iana_at_cisco.com],0,2013-02-18 352,layer2OctetDeltaCount,unsigned64,deltaCounter,current,"The number of layer 2 octets since the previous report (if any) in incoming packets for this Flow at the Observation Point. The number of octets includes layer 2 header(s) and layer 2 payload. # memo: layer 2 version of octetDeltaCount (field #1)",octets,,,[ipfix-iana_at_cisco.com],1,2014-05-02 353,layer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of layer 2 octets in incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point. The number of octets includes layer 2 header(s) and layer 2 payload. # memo: layer 2 version of octetTotalCount (field #85)",octets,,,[ipfix-iana_at_cisco.com],1,2014-05-02 354,ingressUnicastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming unicast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 355,ingressMulticastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming multicast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 356,ingressBroadcastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming broadcast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 357,egressUnicastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming unicast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 358,egressBroadcastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming broadcast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 359,monitoringIntervalStartMilliSeconds,dateTimeMilliseconds,default,current,"The absolute timestamp at which the monitoring interval started. A Monitoring interval is the period of time during which the Metering Process is running.",milliseconds,,,[ipfix-iana_at_cisco.com],0,2013-02-18 360,monitoringIntervalEndMilliSeconds,dateTimeMilliseconds,default,current,"The absolute timestamp at which the monitoring interval ended. A Monitoring interval is the period of time during which the Metering Process is running.",milliseconds,,,[ipfix-iana_at_cisco.com],0,2013-02-18 361,portRangeStart,unsigned16,identifier,current,"The port number identifying the start of a range of ports. A value of zero indicates that the range start is not specified, ie the range is defined in some other way. Additional information on defined TCP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 362,portRangeEnd,unsigned16,identifier,current,"The port number identifying the end of a range of ports. A value of zero indicates that the range end is not specified, ie the range is defined in some other way. Additional information on defined TCP port numbers can be found at [https://www.iana.org/assignments/service-names-port-numbers].",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 363,portRangeStepSize,unsigned16,identifier,current,"The step size in a port range. The default step size is 1, which indicates contiguous ports. A value of zero indicates that the step size is not specified, ie the range is defined in some other way.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 364,portRangeNumPorts,unsigned16,identifier,current,"The number of ports in a port range. A value of zero indicates that the number of ports is not specified, ie the range is defined in some other way.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 365,staMacAddress,macAddress,default,current,The IEEE 802 MAC address of a wireless station (STA).,,,See section 1.4 of [RFC5415] for the definition of STA.,[ipfix-iana_at_cisco.com],1,2014-02-03 366,staIPv4Address,ipv4Address,default,current,The IPv4 address of a wireless station (STA).,,,See section 1.4 of [RFC5415] for the definition of STA.,[ipfix-iana_at_cisco.com],1,2014-02-03 367,wtpMacAddress,macAddress,default,current,The IEEE 802 MAC address of a wireless access point (WTP).,,,See section 1.4 of [RFC5415] for the definition of WTP.,[ipfix-iana_at_cisco.com],1,2014-02-03 368,ingressInterfaceType,unsigned32,identifier,current,"The type of interface where packets of this Flow are being received. The value matches the value of managed object 'ifType' as defined in [https://www.iana.org/assignments/ianaiftype-mib].",,,[https://www.iana.org/assignments/ianaiftype-mib],[ipfix-iana_at_cisco.com],0,2013-02-18 369,egressInterfaceType,unsigned32,identifier,current,"The type of interface where packets of this Flow are being sent. The value matches the value of managed object 'ifType' as defined in [https://www.iana.org/assignments/ianaiftype-mib].",,,[https://www.iana.org/assignments/ianaiftype-mib],[ipfix-iana_at_cisco.com],0,2013-02-18 370,rtpSequenceNumber,unsigned16,,current,The RTP sequence number per [RFC3550].,,,[RFC3550],[ipfix-iana_at_cisco.com],0,2013-02-18 371,userName,string,default,current,User name associated with the flow.,,,,[ipfix-iana_at_cisco.com],0,2013-02-18 372,applicationCategoryName,string,default,current,"An attribute that provides a first level categorization for each Application ID.",,,,[RFC6759],0,2013-02-18 373,applicationSubCategoryName,string,default,current,"An attribute that provides a second level categorization for each Application ID.",,,,[RFC6759],0,2013-02-18 374,applicationGroupName,string,default,current,"An attribute that groups multiple Application IDs that belong to the same networking application.",,,,[RFC6759],0,2013-02-18 375,originalFlowsPresent,unsigned64,deltaCounter,current,"The non-conservative count of Original Flows contributing to this Aggregated Flow. Non-conservative counts need not sum to the original count on re-aggregation.",flows,,,[RFC7015],1,2013-06-25 376,originalFlowsInitiated,unsigned64,deltaCounter,current,"The conservative count of Original Flows whose first packet is represented within this Aggregated Flow. Conservative counts must sum to the original count on re-aggregation.",flows,,,[RFC7015],1,2013-06-25 377,originalFlowsCompleted,unsigned64,deltaCounter,current,"The conservative count of Original Flows whose last packet is represented within this Aggregated Flow. Conservative counts must sum to the original count on re-aggregation.",flows,,,[RFC7015],1,2013-06-25 378,distinctCountOfSourceIPAddress,unsigned64,totalCounter,current,"The count of distinct source IP address values for Original Flows contributing to this Aggregated Flow, without regard to IP version. This Information Element is preferred to the IP-version-specific counters, unless it is important to separate the counts by version.",,,,[RFC7015],0,2013-02-18 379,distinctCountOfDestinationIPAddress,unsigned64,totalCounter,current,"The count of distinct destination IP address values for Original Flows contributing to this Aggregated Flow, without regard to IP version. This Information Element is preferred to the version-specific counters below, unless it is important to separate the counts by version.",,,,[RFC7015],0,2013-02-18 380,distinctCountOfSourceIPv4Address,unsigned32,totalCounter,current,"The count of distinct source IPv4 address values for Original Flows contributing to this Aggregated Flow.",,,,[RFC7015],0,2013-02-18 381,distinctCountOfDestinationIPv4Address,unsigned32,totalCounter,current,"The count of distinct destination IPv4 address values for Original Flows contributing to this Aggregated Flow.",,,,[RFC7015],0,2013-02-18 382,distinctCountOfSourceIPv6Address,unsigned64,totalCounter,current,"The count of distinct source IPv6 address values for Original Flows contributing to this Aggregated Flow.",,,,[RFC7015],0,2013-02-18 383,distinctCountOfDestinationIPv6Address,unsigned64,totalCounter,current,"The count of distinct destination IPv6 address values for Original Flows contributing to this Aggregated Flow.",,,,[RFC7015],0,2013-02-18 384,valueDistributionMethod,unsigned8,,current,"A description of the method used to distribute the counters from Contributing Flows into the Aggregated Flow records described by an associated scope, generally a Template. The method is deemed to apply to all the non-key Information Elements in the referenced scope for which value distribution is a valid operation; if the originalFlowsInitiated and/or originalFlowsCompleted Information Elements appear in the Template, they are not subject to this distribution method, as they each infer their own distribution method. The valueDistributionMethod registry is intended to list a complete set of possible value distribution methods. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-value-distribution-method].",,,,[RFC7015],0,2013-02-18 385,rfc3550JitterMilliseconds,unsigned32,quantity,current,"Interarrival jitter as defined in section 6.4.1 of [RFC3550], measured in milliseconds.",milliseconds,,[RFC3550],[ipfix-iana_at_cisco.com],0,2013-02-18 386,rfc3550JitterMicroseconds,unsigned32,quantity,current,"Interarrival jitter as defined in section 6.4.1 of [RFC3550], measured in microseconds.",microseconds,,[RFC3550],[ipfix-iana_at_cisco.com],0,2013-02-18 387,rfc3550JitterNanoseconds,unsigned32,quantity,current,"Interarrival jitter as defined in section 6.4.1 of [RFC3550], measured in nanoseconds.",nanoseconds,,[RFC3550],[ipfix-iana_at_cisco.com],0,2013-02-18 388,dot1qDEI,boolean,default,current,"The value of the 1-bit Drop Eligible Indicator (DEI) field of the VLAN tag as described in 802.1Q-2011 subclause 9.6. In case of a QinQ frame, it represents the outer tag's DEI field and in case of an IEEE 802.1ad frame it represents the DEI field of the S-TAG. Note: in earlier versions of 802.1Q the same bit field in the incoming packet is occupied by the Canonical Format Indicator (CFI) field, except for S-TAGs.",,,[802.1Q-2011 subclause 9.6],[Yaakov_J_Stein],1,2014-02-03 389,dot1qCustomerDEI,boolean,default,current,"In case of a QinQ frame, it represents the inner tag's Drop Eligible Indicator (DEI) field and in case of an IEEE 802.1ad frame it represents the DEI field of the C-TAG.",,,[802.1Q-2011 subclause 9.6],[Yaakov_J_Stein],1,2014-02-03 390,flowSelectorAlgorithm,unsigned16,identifier,current,"This Information Element identifies the Intermediate Flow Selection Process technique (e.g., Filtering, Sampling) that is applied by the Intermediate Flow Selection Process. Most of these techniques have parameters. Its configuration parameter(s) MUST be clearly specified. Further Information Elements are needed to fully specify packet selection with these methods and all their parameters. Further method identifiers may be added to the flowSelectorAlgorithm registry. It might be necessary to define new Information Elements to specify their parameters. Please note that the purpose of the flow selection techniques described in this document is the improvement of measurement functions as defined in the Scope (Section 1). The Intermediate Flow Selection Process Techniques identifiers are defined at [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flowselectoralgorithm].",,,,[RFC7014],0,2013-06-07 391,flowSelectedOctetDeltaCount,unsigned64,deltaCounter,current,"This Information Element specifies the volume in octets of all Flows that are selected in the Intermediate Flow Selection Process since the previous report.",octets,,,[RFC7014],1,2014-08-13 392,flowSelectedPacketDeltaCount,unsigned64,deltaCounter,current,"This Information Element specifies the volume in packets of all Flows that were selected in the Intermediate Flow Selection Process since the previous report.",packets,,,[RFC7014],1,2014-08-13 393,flowSelectedFlowDeltaCount,unsigned64,deltaCounter,current,"This Information Element specifies the number of Flows that were selected in the Intermediate Flow Selection Process since the last report.",flows,,,[RFC7014],1,2014-08-13 394,selectorIDTotalFlowsObserved,unsigned64,,current,"This Information Element specifies the total number of Flows observed by a Selector, for a specific value of SelectorId. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",flows,,,[RFC7014],0,2013-06-07 395,selectorIDTotalFlowsSelected,unsigned64,,current,"This Information Element specifies the total number of Flows selected by a Selector, for a specific value of SelectorId. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",flows,,,[RFC7014],0,2013-06-07 396,samplingFlowInterval,unsigned64,,current,"This Information Element specifies the number of Flows that are consecutively sampled. A value of 100 means that 100 consecutive Flows are sampled. For example, this Information Element may be used to describe the configuration of a systematic count-based Sampling Selector.",flows,,,[RFC7014],0,2013-06-07 397,samplingFlowSpacing,unsigned64,,current,"This Information Element specifies the number of Flows between two ""samplingFlowInterval""s. A value of 100 means that the next interval starts 100 Flows (which are not sampled) after the current ""samplingFlowInterval"" is over. For example, this Information Element may be used to describe the configuration of a systematic count-based Sampling Selector.",flows,,,[RFC7014],0,2013-06-07 398,flowSamplingTimeInterval,unsigned64,,current,"This Information Element specifies the time interval in microseconds during which all arriving Flows are sampled. For example, this Information Element may be used to describe the configuration of a systematic time-based Sampling Selector.",microseconds,,,[RFC7014],0,2013-06-07 399,flowSamplingTimeSpacing,unsigned64,,current,"This Information Element specifies the time interval in microseconds between two ""flowSamplingTimeInterval""s. A value of 100 means that the next interval starts 100 microseconds (during which no Flows are sampled) after the current ""flowsamplingTimeInterval"" is over. For example, this Information Element may used to describe the configuration of a systematic time-based Sampling Selector.",microseconds,,,[RFC7014],0,2013-06-07 400,hashFlowDomain,unsigned16,identifier,current,"This Information Element specifies the Information Elements that are used by the Hash-based Flow Selector as the Hash Domain.",,,,[RFC7014],0,2013-06-07 401,transportOctetDeltaCount,unsigned64,deltaCounter,current,"The number of octets, excluding IP header(s) and Layer 4 transport protocol header(s), observed for this Flow at the Observation Point since the previous report (if any).",octets,,,[Brian_Trammell],0,2013-08-01 402,transportPacketDeltaCount,unsigned64,deltaCounter,current,"The number of packets containing at least one octet beyond the IP header(s) and Layer 4 transport protocol header(s), observed for this Flow at the Observation Point since the previous report (if any).",packets,,,[Brian_Trammell],0,2013-08-01 403,originalExporterIPv4Address,ipv4Address,,current,"The IPv4 address used by the Exporting Process on an Original Exporter, as seen by the Collecting Process on an IPFIX Mediator. Used to provide information about the Original Observation Points to a downstream Collector.",,,,[RFC7119],0,2013-12-24 404,originalExporterIPv6Address,ipv6Address,,current,"The IPv6 address used by the Exporting Process on an Original Exporter, as seen by the Collecting Process on an IPFIX Mediator. Used to provide information about the Original Observation Points to a downstream Collector.",,,,[RFC7119],0,2013-12-24 405,originalObservationDomainId,unsigned32,identifier,current,"The Observation Domain ID reported by the Exporting Process on an Original Exporter, as seen by the Collecting Process on an IPFIX Mediator. Used to provide information about the Original Observation Domain to a downstream Collector. When cascading through multiple Mediators, this identifies the initial Observation Domain in the cascade.",,,,[RFC7119],0,2013-12-24 406,intermediateProcessId,unsigned32,identifier,current,"Description: An identifier of an Intermediate Process that is unique per IPFIX Device. Typically, this Information Element is used for limiting the scope of other Information Elements. Note that process identifiers may be assigned dynamically; that is, an Intermediate Process may be restarted with a different ID.",,,,[RFC7119],0,2013-12-24 407,ignoredDataRecordTotalCount,unsigned64,totalCounter,current,"Description: The total number of received Data Records that the Intermediate Process did not process since the (re-)initialization of the Intermediate Process; includes only Data Records not examined or otherwise handled by the Intermediate Process due to resource constraints, not Data Records that were examined or otherwise handled by the Intermediate Process but those that merely do not contribute to any exported Data Record due to the operations performed by the Intermediate Process.",,,,[RFC7119],0,2013-12-24 408,dataLinkFrameType,unsigned16,flags,current,"This Information Element specifies the type of the selected data link frame. Data link types are defined in the dataLinkFrameType registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-data-link-frame-type]. Further values may be assigned by IANA. Note that the assigned values are bits so that multiple observations can be OR'd together. The data link layer is defined in [ISO/IEC.7498-1:1994].",,,[IEEE802.3][IEEE802.11][ISO/IEC.7498-1:1994],[RFC7133],0,2014-01-11 409,sectionOffset,unsigned16,quantity,current,"This Information Element specifies the offset of the packet section (e.g., dataLinkFrameSection, ipHeaderPacketSection, ipPayloadPacketSection, mplsLabelStackSection, and mplsPayloadPacketSection). If this Information Element is omitted, it defaults to zero (i.e., no offset). If multiple sectionOffset Information Elements are specified within a single Template, then they apply to the packet section Information Elements in order: the first sectionOffset applies to the first packet section, the second to the second, and so on. Note that the ""closest"" sectionOffset and packet section Information Elements within a given Template are not necessarily related. If there are fewer sectionOffset Information Elements than packet section Information Elements, then subsequent packet section Information Elements have no offset, i.e., a sectionOffset of zero applies to those packet section Information Elements. If there are more sectionOffset Information Elements than the number of packet section Information Elements, then the additional sectionOffset Information Elements are meaningless.",,,,[RFC7133],0,2014-01-11 410,sectionExportedOctets,unsigned16,quantity,current,"This Information Element specifies the observed length of the packet section (e.g., dataLinkFrameSection, ipHeaderPacketSection, ipPayloadPacketSection, mplsLabelStackSection, and mplsPayloadPacketSection) when padding is used. The packet section may be of a fixed size larger than the sectionExportedOctets. In this case, octets in the packet section beyond the sectionExportedOctets MUST follow the [RFC7011] rules for padding (i.e., be composed of zero (0) valued octets).",,,[RFC7011],[RFC7133],0,2014-01-11 411,dot1qServiceInstanceTag,octetArray,default,current,"This Information Element, which is 16 octets long, represents the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q]. It encodes the Backbone Service Instance Priority Code Point (I-PCP), Backbone Service Instance Drop Eligible Indicator (I-DEI), Use Customer Addresses (UCAs), Backbone Service Instance Identifier (I-SID), Encapsulated Customer Destination Address (C-DA), Encapsulated Customer Source Address (C-SA), and reserved fields. The structure and semantics within the Tag Control Information field are defined in [IEEE802.1Q].",,,[IEEE802.1Q],[RFC7133],1,2014-05-02 412,dot1qServiceInstanceId,unsigned32,identifier,current,"The value of the 24-bit Backbone Service Instance Identifier (I-SID) portion of the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q].",,0-0xFFFFFF,[IEEE802.1Q],[RFC7133],1,2014-05-02 413,dot1qServiceInstancePriority,unsigned8,identifier,current,"The value of the 3-bit Backbone Service Instance Priority Code Point (I-PCP) portion of the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q].",,0-7,[IEEE802.1Q],[RFC7133],1,2014-05-02 414,dot1qCustomerSourceMacAddress,macAddress,default,current,"The value of the Encapsulated Customer Source Address (C-SA) portion of the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q].",,,[IEEE802.1Q],[RFC7133],1,2014-05-02 415,dot1qCustomerDestinationMacAddress,macAddress,default,current,"The value of the Encapsulated Customer Destination Address (C-DA) portion of the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q].",,,[IEEE802.1Q],[RFC7133],1,2014-05-02 416,,,,deprecated,"Duplicate of Information Element ID 352, layer2OctetDeltaCount.",,,[RFC5477],,2,2014-05-13 417,postLayer2OctetDeltaCount,unsigned64,deltaCounter,current,"The definition of this Information Element is identical to the definition of the layer2OctetDeltaCount Information Element, except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point. This Information Element is the layer 2 version of postOctetDeltaCount (ElementId #23).",octets,,[RFC5477],[RFC7133],1,2014-05-02 418,postMCastLayer2OctetDeltaCount,unsigned64,deltaCounter,current,"The number of layer 2 octets since the previous report (if any) in outgoing multicast packets sent for packets of this Flow by a multicast daemon within the Observation Domain. This property cannot necessarily be observed at the Observation Point but may be retrieved by other means. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of postMCastOctetDeltaCount (ElementId #20).",octets,,[RFC5477],[RFC7133],1,2014-05-02 419,,,,deprecated,"Duplicate of Information Element ID 353, layer2OctetTotalCount.",,,[RFC5477],,2,2014-05-13 420,postLayer2OctetTotalCount,unsigned64,totalCounter,current,"The definition of this Information Element is identical to the definition of the layer2OctetTotalCount Information Element, except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point. This Information Element is the layer 2 version of postOctetTotalCount (ElementId #171).",octets,,[RFC5477],[RFC7133],1,2014-05-02 421,postMCastLayer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of layer 2 octets in outgoing multicast packets sent for packets of this Flow by a multicast daemon in the Observation Domain since the Metering Process (re-)initialization. This property cannot necessarily be observed at the Observation Point but may be retrieved by other means. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of postMCastOctetTotalCount (ElementId #175).",octets,,[RFC5477],[RFC7133],1,2014-05-02 422,minimumLayer2TotalLength,unsigned64,,current,"Layer 2 length of the smallest packet observed for this Flow. The packet length includes the length of the layer 2 header(s) and the length of the layer 2 payload. This Information Element is the layer 2 version of minimumIpTotalLength (ElementId #25).",octets,,[RFC5477],[RFC7133],1,2014-05-02 423,maximumLayer2TotalLength,unsigned64,,current,"Layer 2 length of the largest packet observed for this Flow. The packet length includes the length of the layer 2 header(s) and the length of the layer 2 payload. This Information Element is the layer 2 version of maximumIpTotalLength (ElementId #26).",octets,,[RFC5477],[RFC7133],1,2014-05-02 424,droppedLayer2OctetDeltaCount,unsigned64,deltaCounter,current,"The number of layer 2 octets since the previous report (if any) in packets of this Flow dropped by packet treatment. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of droppedOctetDeltaCount (ElementId #132).",octets,,[RFC5477],[RFC7133],1,2014-05-02 425,droppedLayer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in observed layer 2 packets (including the layer 2 header) that were dropped by packet treatment since the (re-)initialization of the Metering Process. This Information Element is the layer 2 version of droppedOctetTotalCount (ElementId #134).",octets,,[RFC5477],[RFC7133],1,2014-05-02 426,ignoredLayer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in observed layer 2 packets (including the layer 2 header) that the Metering Process did not process since the (re-)initialization of the Metering Process. This Information Element is the layer 2 version of ignoredOctetTotalCount (ElementId #165).",octets,,[RFC5477],[RFC7133],1,2014-05-02 427,notSentLayer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in observed layer 2 packets (including the layer 2 header) that the Metering Process did not process since the (re-)initialization of the Metering Process. This Information Element is the layer 2 version of notSentOctetTotalCount (ElementId #168).",octets,,[RFC5477],[RFC7133],1,2014-05-02 428,layer2OctetDeltaSumOfSquares,unsigned64,deltaCounter,current,"The sum of the squared numbers of layer 2 octets per incoming packet since the previous report (if any) for this Flow at the Observation Point. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of octetDeltaSumOfSquares (ElementId #198).",octets,,[RFC5477],[RFC7133],1,2014-05-02 429,layer2OctetTotalSumOfSquares,unsigned64,totalCounter,current,"The total sum of the squared numbers of layer 2 octets in incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of octetTotalSumOfSquares (ElementId #199).",octets,,[RFC5477],[RFC7133],1,2014-05-02 430,layer2FrameDeltaCount,unsigned64,deltaCounter,current,"The number of incoming layer 2 frames since the previous report (if any) for this Flow at the Observation Point.",frames,,,[ipfix-iana_at_cisco.com],0,2014-05-02 431,layer2FrameTotalCount,unsigned64,totalCounter,current,"The total number of incoming layer 2 frames for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",frames,,,[ipfix-iana_at_cisco.com],0,2014-05-02 432,pseudoWireDestinationIPv4Address,ipv4Address,default,current,The destination IPv4 address of the PSN tunnel carrying the pseudowire.,,,[RFC3985],[ipfix-iana_at_cisco.com],0,2014-05-28 433,ignoredLayer2FrameTotalCount,unsigned64,totalCounter,current,"The total number of observed layer 2 frames that the Metering Process did not process since the (re-)initialization of the Metering Process. This Information Element is the layer 2 version of ignoredPacketTotalCount (ElementId #164).",frames,,,[ipfix-iana_at_cisco.com],0,2014-06-27 434,mibObjectValueInteger,signed32,quantity,current,"An IPFIX Information Element that denotes that the integer value of a MIB object will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with the Base syntax of Integer32 and INTEGER with IPFIX reduced-size encoding used as required. The value is encoded as per the standard IPFIX Abstract Data Type of signed32.",,,,[RFC8038],1,2017-04-30 435,mibObjectValueOctetString,octetArray,default,current,"An IPFIX Information Element that denotes that an Octet String or Opaque value of a MIB object will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with the Base syntax of OCTET STRING and Opaque. The value is encoded as per the standard IPFIX Abstract Data Type of octetArray.",,,,[RFC8038],0,2015-12-13 436,mibObjectValueOID,octetArray,default,current,"An IPFIX Information Element that denotes that an Object Identifier or OID value of a MIB object will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with the Base syntax of OBJECT IDENTIFIER. Note: In this case, the ""mibObjectIdentifier"" defines which MIB object is being exported, and the ""mibObjectValueOID"" field will contain the OID value of that MIB object. The mibObjectValueOID Information Element is encoded as ASN.1/BER [X.690] in an octetArray.",,,,[RFC8038],0,2015-12-13 437,mibObjectValueBits,octetArray,flags,current,"An IPFIX Information Element that denotes that a set of Enumerated flags or bits from a MIB object will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with the Base syntax of BITS. The flags or bits are encoded as per the standard IPFIX Abstract Data Type of octetArray, with sufficient length to accommodate the required number of bits. If the number of bits is not an integer multiple of octets, then the most significant bits at the end of the octetArray MUST be set to 0.",,,,[RFC8038],0,2015-12-13 438,mibObjectValueIPAddress,ipv4Address,default,current,"An IPFIX Information Element that denotes that the IPv4 address value of a MIB object will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with the Base syntax of IpAddress. The value is encoded as per the standard IPFIX Abstract Data Type of ipv4Address.",,,,[RFC8038],0,2015-12-13 439,mibObjectValueCounter,unsigned64,snmpCounter,current,"An IPFIX Information Element that denotes that the counter value of a MIB object will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with the Base syntax of Counter32 or Counter64 with IPFIX reduced-size encoding used as required. The value is encoded as per the standard IPFIX Abstract Data Type of unsigned64.",,,,[RFC8038],0,2015-12-13 440,mibObjectValueGauge,unsigned32,snmpGauge,current,"An IPFIX Information Element that denotes that the Gauge value of a MIB object will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with the Base syntax of Gauge32. The value is encoded as per the standard IPFIX Abstract Data Type of unsigned32. This value represents a non-negative integer that may increase or decrease but that shall never exceed a maximum value or fall below a minimum value.",,,,[RFC8038],0,2015-12-13 441,mibObjectValueTimeTicks,unsigned32,quantity,current,"An IPFIX Information Element that denotes that the TimeTicks value of a MIB object will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with the Base syntax of TimeTicks. The value is encoded as per the standard IPFIX Abstract Data Type of unsigned32.",,,,[RFC8038],1,2017-04-30 442,mibObjectValueUnsigned,unsigned32,quantity,current,"An IPFIX Information Element that denotes that an unsigned integer value of a MIB object will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with the Base syntax of unsigned32 with IPFIX reduced-size encoding used as required. The value is encoded as per the standard IPFIX Abstract Data Type of unsigned32.",,,,[RFC8038],1,2017-04-30 443,mibObjectValueTable,subTemplateList,list,current,"An IPFIX Information Element that denotes that a complete or partial conceptual table will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with a syntax of SEQUENCE OF. This is encoded as a subTemplateList of mibObjectValue Information Elements. The Template specified in the subTemplateList MUST be an Options Template and MUST include all the objects listed in the INDEX clause as Scope Fields.",,,,[RFC8038],1,2017-04-30 444,mibObjectValueRow,subTemplateList,list,current,"An IPFIX Information Element that denotes that a single row of a conceptual table will be exported. The MIB Object Identifier (""mibObjectIdentifier"") for this field MUST be exported in a MIB Field Option or via another means. This Information Element is used for MIB objects with a syntax of SEQUENCE. This is encoded as a subTemplateList of mibObjectValue Information Elements. The subTemplateList exported MUST contain exactly one row (i.e., one instance of the subTemplate). The Template specified in the subTemplateList MUST be an Options Template and MUST include all the objects listed in the INDEX clause as Scope Fields.",,,,[RFC8038],0,2015-12-13 445,mibObjectIdentifier,octetArray,default,current,"An IPFIX Information Element that denotes that a MIB Object Identifier (MIB OID) is exported in the (Options) Template Record. The mibObjectIdentifier Information Element contains the OID assigned to the MIB object type definition encoded as ASN.1/BER [X.690].",,,,[RFC8038],0,2015-12-13 446,mibSubIdentifier,unsigned32,identifier,current,A non-negative sub-identifier of an Object Identifier (OID).,,,,[RFC8038],0,2015-12-13 447,mibIndexIndicator,unsigned64,flags,current,"A set of bit fields that is used for marking the Information Elements of a Data Record that serve as INDEX MIB objects for an indexed columnar MIB object. Each bit represents an Information Element in the Data Record, with the n-th least significant bit representing the n-th Information Element. A bit set to 1 indicates that the corresponding Information Element is an index of the columnar object represented by the mibObjectValue. A bit set to 0 indicates that this is not the case. If the Data Record contains more than 64 Information Elements, the corresponding Template SHOULD be designed such that all index fields are among the first 64 Information Elements, because the mibIndexIndicator only contains 64 bits. If the Data Record contains less than 64 Information Elements, then the extra bits in the mibIndexIndicator for which no corresponding Information Element exists MUST have the value 0 and must be disregarded by the Collector. This Information Element may be exported with IPFIX reduced-size encoding.",,,,[RFC8038],0,2015-12-13 448,mibCaptureTimeSemantics,unsigned8,identifier,current,"Indicates when in the lifetime of the Flow the MIB value was retrieved from the MIB for a mibObjectIdentifier. This is used to indicate if the value exported was collected from the MIB closer to Flow creation or Flow export time and refers to the Timestamp fields included in the same Data Record. This field SHOULD be used when exporting a mibObjectValue that specifies counters or statistics. If the MIB value was sampled by SNMP prior to the IPFIX Metering Process or Exporting Process retrieving the value (i.e., the data is already stale) and it is important to know the exact sampling time, then an additional observationTime* element should be paired with the OID using IPFIX Structured Data [RFC6313]. Similarly, if different MIB capture times apply to different mibObjectValue elements within the Data Record, then individual mibCaptureTimeSemantics Information Elements should be paired with each OID using IPFIX Structured Data. Values are listed in the mibCaptureTimeSemantics registry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-mib-capture-time-semantics].",,,,[RFC8038],0,2015-12-13 449,mibContextEngineID,octetArray,default,current,"A mibContextEngineID that specifies the SNMP engine ID for a MIB field being exported over IPFIX. Definition as per [RFC3411], Section 3.3.",,,,[RFC8038],0,2015-12-13 450,mibContextName,string,default,current,"An Information Element that denotes that a MIB context name is specified for a MIB field being exported over IPFIX. Reference [RFC3411], Section 3.3.",,,,[RFC8038],0,2015-12-13 451,mibObjectName,string,default,current,"The name (called a descriptor in [RFC2578] of an object type definition.",,,,[RFC8038],0,2015-12-13 452,mibObjectDescription,string,default,current,"The value of the DESCRIPTION clause of a MIB object type definition.",,,,[RFC8038],0,2015-12-13 453,mibObjectSyntax,string,default,current,"The value of the SYNTAX clause of a MIB object type definition, which may include a textual convention or sub-typing. See [RFC2578].",,,,[RFC8038],0,2015-12-13 454,mibModuleName,string,default,current,"The textual name of the MIB module that defines a MIB object.",,,,[RFC8038],0,2015-12-13 455,mobileIMSI,string,default,current,"The International Mobile Subscription Identity (IMSI). The IMSI is a decimal digit string with up to a maximum of 15 ASCII/UTF-8 encoded digits (0x30 - 0x39).",,,[3GPP TS 23.003] Section 3 and [ITU-T E.164].,[ipfix-iana_at_cisco.com],0,2015-12-15 456,mobileMSISDN,string,default,current,"The Mobile Station International Subscriber Directory Number (MSISDN). The MSISDN is a decimal digit string with up to a maximum of 15 ASCII/UTF-8 encoded digits (0x30 - 0x39).",,,[3GPP TS 23.003] Section 3 and [ITU-T E.164].,[ipfix-iana_at_cisco.com],0,2015-12-15 457,httpStatusCode,unsigned16,identifier,current,"The HTTP Response Status Code, as defined in section 6 of [RFC7231], associated with a flow. Implies that the flow record represents a flow containing an HTTP Response.",,0-999,[RFC7231],[Andrew_Feren],0,2016-04-28 458,sourceTransportPortsLimit,unsigned16,quantity,current,"This Information Element contains the maximum number of IP source transport ports that can be used by an end user when sending IP packets; each user is associated with one or more (source) IPv4 or IPv6 addresses. This Information Element is particularly useful in address-sharing deployments that adhere to REQ-4 of [RFC6888]. Limiting the number of ports assigned to each user ensures fairness among users and mitigates the denial-of-service attack that a user could launch against other users through the address-sharing device in order to grab more ports.",ports,1-65535,,[RFC8045][RFC Errata 5009],1,2017-08-01 459,httpRequestMethod,string,,current,"The HTTP request method, as defined in section 4 of [RFC7231], associated with a flow. String with up to 8 UTF-8 characters.",,,,[Felix_Erlacher],0,2016-11-15 460,httpRequestHost,string,,current,"The HTTP request host, as defined in section 5.4 of [RFC7230] or, in the case of HTTP/2, the content of the :authority pseudo-header field as defined in section 8.1.2.3 of [RFC7240]. Encoded in UTF-8.",,,,[Felix_Erlacher],0,2016-11-15 461,httpRequestTarget,string,,current,"The HTTP request target, as defined in section 2 of [RFC7231] and in section 5.3 of [RFC7230], associated with a flow. Or the HTTP/2 "":path"" pseudo-header field as defined in section 8.1.2.3 of [RFC7240]. Encoded in UTF-8.",,,,[Felix_Erlacher],0,2016-11-15 462,httpMessageVersion,string,,current,"The version of an HTTP/1.1 message as indicated by the HTTP-version field, defined in section 2.6 of [RFC7230], or the version identification of an HTTP/2 frame as defined in [RFC7240] section 3.1. The length of this field is limited to 10 characters, UTF-8 encoded.",,,,[Felix_Erlacher],0,2016-11-15 463,natInstanceID,unsigned32,identifier,current,This Information Element uniquely identifies an Instance of the NAT that runs on a NAT middlebox function after the packet passes the Observation Point. natInstanceID is defined in [RFC7659].,,,See [RFC791] for the definition of the IPv4 source address field. See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-03-15 464,internalAddressRealm,octetArray,identifier,current,"This Information Element represents the internal address realm where the packet is originated from or destined to. By definition, a NAT mapping can be created from two address realms, one from internal and one from external. Realms are implementation dependent and can represent a Virtual Routing and Forwarding (VRF) ID, a VLAN ID, or some unique identifier. Realms are optional and, when left unspecified, would mean that the external and internal realms are the same.",,,See [RFC791] for the definition of the IPv4 source address field. See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-03-15 465,externalAddressRealm,octetArray,identifier,current,"This Information Element represents the external address realm where the packet is originated from or destined to. The detailed definition is in the internal address realm as specified above.",,,See [RFC791] for the definition of the IPv4 source address field. See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-03-15 466,natQuotaExceededEvent,unsigned32,identifier,current,"This Information Element identifies the type of a NAT Quota Exceeded event. Values for this Information Element are listed in the ""NAT Quota Exceeded Event Type"" registry, see [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-nat-quota-exceeded-event].",,,See [RFC791] for the definition of the IPv4 source address field. See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-03-15 467,natThresholdEvent,unsigned32,identifier,current,"This Information Element identifies a type of a NAT Threshold event. Values for this Information Element are listed in the ""NAT Threshold Event Type"" registry, see [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-nat-threshold-event].",,,See [RFC791] for the definition of the IPv4 source address field. See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-03-15 468,httpUserAgent,string,default,current,The HTTP User-Agent header field as defined in section 5.5.3 of [RFC7231]. Encoded in UTF-8.,,,[RFC7231],[Andrew_Feren],0,2017-04-19 469,httpContentType,string,default,current,The HTTP Content-Type header field as defined in section 3.1.1.5 of [RFC7231]. Encoded in UTF-8.,,,[RFC7231],[Andrew_Feren],0,2017-04-19 470,httpReasonPhrase,string,default,current,The HTTP reason phrase as defined in section 6.1 of of [RFC7231].,,,[RFC7231],[Felix_Erlacher],0,2017-06-19 471,maxSessionEntries,unsigned32,identifier,current,"This element represents the maximum session entries that can be created by the NAT device.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 472,maxBIBEntries,unsigned32,identifier,current,"This element represents the maximum BIB entries that can be created by the NAT device.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 473,maxEntriesPerUser,unsigned32,identifier,current,"This element represents the maximum NAT entries that can be created per user by the NAT device.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 474,maxSubscribers,unsigned32,identifier,current,"This element represents the maximum subscribers or maximum hosts that are allowed by the NAT device.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 475,maxFragmentsPendingReassembly,unsigned32,identifier,current,"This element represents the maximum fragments that the NAT device can store for reassembling the packet.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 476,addressPoolHighThreshold,unsigned32,identifier,current,"This element represents the high threshold value of the number of public IP addresses in the address pool.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 477,addressPoolLowThreshold,unsigned32,identifier,current,"This element represents the low threshold value of the number of public IP addresses in the address pool.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 478,addressPortMappingHighThreshold,unsigned32,identifier,current,"This element represents the high threshold value of the number of address and port mappings.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 479,addressPortMappingLowThreshold,unsigned32,identifier,current,"This element represents the low threshold value of the number of address and port mappings.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 480,addressPortMappingPerUserHighThreshold,unsigned32,identifier,current,"This element represents the high threshold value of the number of address and port mappings that a single user is allowed to create on a NAT device.",,,See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.,[RFC8158],0,2017-12-01 481,globalAddressMappingHighThreshold,unsigned32,identifier,current,"This element represents the high threshold value of the number of address and port mappings that a single user is allowed to create on a NAT device in a paired address pooling behavior.",,,"See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes. See [RFC4787] for the definition of paired address pooling behavior.",[RFC8158],0,2017-12-01 482,vpnIdentifier,octetArray,default,current,"VPN ID in the format specified by [RFC2685]. The size of this Information Element is 7 octets.",,,[RFC2685],[ipfix-iana_at_cisco.com],0,2018-07-10 483,bgpCommunity,unsigned32,identifier,current,BGP community as defined in [RFC1997],,,[RFC1997],[RFC8549],0,2019-01-18 484,bgpSourceCommunityList,basicList,list,current,"basicList of zero or more bgpCommunity IEs, containing the BGP communities corresponding with source IP address of a specific flow",,,"[RFC6313] [RFC1997]",[RFC8549],0,2019-01-18 485,bgpDestinationCommunityList,basicList,list,current,"basicList of zero or more bgpCommunity IEs, containing the BGP communities corresponding with destination IP address of a specific flow",,,"[RFC6313] [RFC1997]",[RFC8549],0,2019-01-18 486,bgpExtendedCommunity,octetArray,default,current,"BGP Extended Community as defined in [RFC4360]; the size of this IE MUST be 8 octets",,,[RFC4360],[RFC8549],0,2019-01-18 487,bgpSourceExtendedCommunityList,basicList,list,current,"basicList of zero or more bgpExtendedCommunity IEs, containing the BGP Extended Communities corresponding with source IP address of a specific flow",,,"[RFC6313] [RFC4360]",[RFC8549],0,2019-01-18 488,bgpDestinationExtendedCommunityList,basicList,list,current,"basicList of zero or more bgpExtendedCommunity IEs, containing the BGP Extended Communities corresponding with destination IP address of a specific flow",,,"[RFC6313] [RFC4360]",[RFC8549],0,2019-01-18 489,bgpLargeCommunity,octetArray,default,current,"BGP Large Community as defined in [RFC8092]; the size of this IE MUST be 12 octets.",,,[RFC8092],[RFC8549],0,2019-01-18 490,bgpSourceLargeCommunityList,basicList,list,current,"basicList of zero or more bgpLargeCommunity IEs, containing the BGP Large Communities corresponding with source IP address of a specific flow",,,"[RFC6313] [RFC8092]",[RFC8549],0,2019-01-18 491,bgpDestinationLargeCommunityList,basicList,list,current,"basicList of zero or more bgpLargeCommunity IEs, containing the BGP Large Communities corresponding with destination IP address of a specific flow",,,"[RFC6313] [RFC8092]",[RFC8549],0,2019-01-18 492,srhFlagsIPv6,unsigned8,flags,current,"The 8-bit flags defined in the SRH (Section 2 of [RFC8754]). Assigned flags and their meanings are provided in the [""Segment Routing Header Flags""] IANA registry.",,,"See the assignments in the ""Segment Routing Header Flags"" IANA registry at [https://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml#segment-routing-header-flags]. See also [RFC8754] for the SRH specification.",[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 493,srhTagIPv6,unsigned16,identifier,current,The 16-bit tag field defined in the SRH (Section 2 of [RFC8754]). A tag is used to mark a packet as part of a class or group of packets sharing the same set of properties.,,,See Section 2 of [RFC8754] for more details about the tag.,[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 494,srhSegmentIPv6,ipv6Address,default,current,The 128-bit IPv6 address that represents a SRv6 segment.,,,"Specified in Section 1 of [RFC8402] and mentioned in ""Segment List"" in Section 2 of [RFC8754].",[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 495,srhActiveSegmentIPv6,ipv6Address,default,current,The 128-bit IPv6 address that represents the active SRv6 segment.,,,See Section 2 of [RFC8402] for the definition of active segment.,[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 496,srhSegmentIPv6BasicList,basicList,list,current,"The Ordered basicList [RFC6313] of zero or more 128-bit IPv6 addresses in the SRH that represents the SRv6 Segment List. As specified in Section 2 of [RFC8754], the Segment List is encoded starting from the last segment of the SR Policy. That is, the first element of the Segment List (Segment List[0]) contains the last segment of the SR Policy, the second element contains the penultimate segment of the SR Policy, and so on.",,,See Section 2 of [RFC8754] for more details about the SRv6 Segment List.,[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 497,srhSegmentIPv6ListSection,octetArray,default,current,The SRH Segment List as defined in Section 2 of [RFC8754] as a series of octets in IPFIX.,,,See Section 2 of [RFC8754] for more details about the SRv6 Segment List.,[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 498,srhSegmentsIPv6Left,unsigned8,quantity,current,8-bit unsigned integer defining the number of segments remaining to reach the end of the Segment List in the SRH.,,,"Specified by the ""Segments Left"" field in Section 4.4 of [RFC8200] and mentioned in Section 2 of [RFC8754]).",[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 499,srhIPv6Section,octetArray,default,current,The SRH and its TLVs as defined in Section 2 of [RFC8754] as a series of octets in IPFIX.,,,See Section 2 of [RFC8754] for more details about the structure of an SRH.,[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 500,srhIPv6ActiveSegmentType,unsigned8,identifier,current,"The designator of the routing protocol or PCEP extension from where the active SRv6 segment has been learned from. Values for this Information Element are listed in the ""IPFIX IPv6 SRH Segment type"" subregistry. See [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-ipv6-srh-segment-type].",,,See the assigned types in [https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-ipv6-srh-segment-type].,[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 501,srhSegmentIPv6LocatorLength,unsigned8,default,current,The SRH segment IPv6 locator length specified as the number of significant bits. Together with srhSegmentIPv6 it enables the calculation of the SRv6 Locator.,,,See Section 3.1 of [RFC8986] for more details about the SID format.,[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 502,srhSegmentIPv6EndpointBehavior,unsigned16,identifier,current,"The 16-bit unsigned integer that represents a SRv6 Endpoint behavior as per Section 4 of [RFC8986]. Assigned values and their meanings are provided in the [""SRV6 Endpoint Behavior""] registry.",,,"See the assigned behaviors at the ""SRv6 Endpoint Behavior"" registry available at [https://www.iana.org/assignments/segment-routing/segment-routing.xhtml#srv6-endpoint-behaviors]. See Section 4 of [RFC8986] for more details about the endpoint behaviors processing.",[RFC-ietf-opsawg-ipfix-srv6-srh-14],0,2023-06-08 503-32767,Unassigned,,,,,,,,,, pavel-odintsov-fastnetmon-394fbe0/src/ipfix_fields/ipfix_rfc.cpp000066400000000000000000000673701520703010000251700ustar00rootroot00000000000000#include #include #include "ipfix_rfc.hpp" /* This file is autogenerated with script ipfix_csv_processor.pl */ /* Please do not edit it directly */ std::string ipfix_information_element_t::get_name() { return this->name; } unsigned int ipfix_information_element_t::get_length() { return this->length; } ipfix_information_element_t::ipfix_information_element_t(std::string name, unsigned int length) { this->name = name; this->length = length; } ipfix_information_element_t::ipfix_information_element_t() { this->name = std::string(""); this->length = 0; } std::string ipfix_information_database::get_name_by_id(unsigned int field_id) { auto itr = database.find(field_id); if (itr == database.end()) { return std::string(""); } return itr->second.get_name(); } unsigned int ipfix_information_database::get_length_by_id(unsigned int field_id) { auto itr = database.find(field_id); if (itr == database.end()) { return 0; } return itr->second.get_length(); } bool ipfix_information_database::add_element(unsigned int field_id, std::string name, unsigned int length) { auto itr = database.find(field_id); // Duplicate ID's strictly prohibited if (itr != database.end()) { return false; } database[field_id] = ipfix_information_element_t(name, length); return true; } ipfix_information_database::ipfix_information_database() { this->add_element(0, "Reserved", 0); this->add_element(1, "octetDeltaCount", 8); this->add_element(2, "packetDeltaCount", 8); this->add_element(3, "deltaFlowCount", 8); this->add_element(4, "protocolIdentifier", 1); this->add_element(5, "ipClassOfService", 1); this->add_element(6, "tcpControlBits", 2); this->add_element(7, "sourceTransportPort", 2); this->add_element(8, "sourceIPv4Address", 4); this->add_element(9, "sourceIPv4PrefixLength", 1); this->add_element(10, "ingressInterface", 4); this->add_element(11, "destinationTransportPort", 2); this->add_element(12, "destinationIPv4Address", 4); this->add_element(13, "destinationIPv4PrefixLength", 1); this->add_element(14, "egressInterface", 4); this->add_element(15, "ipNextHopIPv4Address", 4); this->add_element(16, "bgpSourceAsNumber", 4); this->add_element(17, "bgpDestinationAsNumber", 4); this->add_element(18, "bgpNextHopIPv4Address", 4); this->add_element(19, "postMCastPacketDeltaCount", 8); this->add_element(20, "postMCastOctetDeltaCount", 8); this->add_element(21, "flowEndSysUpTime", 4); this->add_element(22, "flowStartSysUpTime", 4); this->add_element(23, "postOctetDeltaCount", 8); this->add_element(24, "postPacketDeltaCount", 8); this->add_element(25, "minimumIpTotalLength", 8); this->add_element(26, "maximumIpTotalLength", 8); this->add_element(27, "sourceIPv6Address", 16); this->add_element(28, "destinationIPv6Address", 16); this->add_element(29, "sourceIPv6PrefixLength", 1); this->add_element(30, "destinationIPv6PrefixLength", 1); this->add_element(31, "flowLabelIPv6", 4); this->add_element(32, "icmpTypeCodeIPv4", 2); this->add_element(33, "igmpType", 1); this->add_element(34, "samplingInterval", 4); this->add_element(35, "samplingAlgorithm", 1); this->add_element(36, "flowActiveTimeout", 2); this->add_element(37, "flowIdleTimeout", 2); this->add_element(38, "engineType", 1); this->add_element(39, "engineId", 1); this->add_element(40, "exportedOctetTotalCount", 8); this->add_element(41, "exportedMessageTotalCount", 8); this->add_element(42, "exportedFlowRecordTotalCount", 8); this->add_element(43, "ipv4RouterSc", 4); this->add_element(44, "sourceIPv4Prefix", 4); this->add_element(45, "destinationIPv4Prefix", 4); this->add_element(46, "mplsTopLabelType", 1); this->add_element(47, "mplsTopLabelIPv4Address", 4); this->add_element(48, "samplerId", 1); this->add_element(49, "samplerMode", 1); this->add_element(50, "samplerRandomInterval", 4); this->add_element(51, "classId", 1); this->add_element(52, "minimumTTL", 1); this->add_element(53, "maximumTTL", 1); this->add_element(54, "fragmentIdentification", 4); this->add_element(55, "postIpClassOfService", 1); this->add_element(56, "sourceMacAddress", 0); this->add_element(57, "postDestinationMacAddress", 0); this->add_element(58, "vlanId", 2); this->add_element(59, "postVlanId", 2); this->add_element(60, "ipVersion", 1); this->add_element(61, "flowDirection", 1); this->add_element(62, "ipNextHopIPv6Address", 16); this->add_element(63, "bgpNextHopIPv6Address", 16); this->add_element(64, "ipv6ExtensionHeaders", 4); this->add_element(65, "Reserved", 0); this->add_element(66, "Reserved", 0); this->add_element(67, "Reserved", 0); this->add_element(68, "Reserved", 0); this->add_element(69, "Reserved", 0); this->add_element(70, "mplsTopLabelStackSection", 0); this->add_element(71, "mplsLabelStackSection2", 0); this->add_element(72, "mplsLabelStackSection3", 0); this->add_element(73, "mplsLabelStackSection4", 0); this->add_element(74, "mplsLabelStackSection5", 0); this->add_element(75, "mplsLabelStackSection6", 0); this->add_element(76, "mplsLabelStackSection7", 0); this->add_element(77, "mplsLabelStackSection8", 0); this->add_element(78, "mplsLabelStackSection9", 0); this->add_element(79, "mplsLabelStackSection10", 0); this->add_element(80, "destinationMacAddress", 0); this->add_element(81, "postSourceMacAddress", 0); this->add_element(82, "interfaceName", 0); this->add_element(83, "interfaceDescription", 0); this->add_element(84, "samplerName", 0); this->add_element(85, "octetTotalCount", 8); this->add_element(86, "packetTotalCount", 8); this->add_element(87, "flagsAndSamplerId", 4); this->add_element(88, "fragmentOffset", 2); this->add_element(89, "forwardingStatus", 1); this->add_element(90, "mplsVpnRouteDistinguisher", 0); this->add_element(91, "mplsTopLabelPrefixLength", 1); this->add_element(92, "srcTrafficIndex", 4); this->add_element(93, "dstTrafficIndex", 4); this->add_element(94, "applicationDescription", 0); this->add_element(95, "applicationId", 0); this->add_element(96, "applicationName", 0); this->add_element(97, "Assigned for NetFlow v9 compatibility", 0); this->add_element(98, "postIpDiffServCodePoint", 1); this->add_element(99, "multicastReplicationFactor", 4); this->add_element(100, "className", 0); this->add_element(101, "classificationEngineId", 1); this->add_element(102, "layer2packetSectionOffset", 2); this->add_element(103, "layer2packetSectionSize", 2); this->add_element(104, "layer2packetSectionData", 0); this->add_element(105, "Reserved", 0); this->add_element(106, "Reserved", 0); this->add_element(107, "Reserved", 0); this->add_element(108, "Reserved", 0); this->add_element(109, "Reserved", 0); this->add_element(110, "Reserved", 0); this->add_element(111, "Reserved", 0); this->add_element(112, "Reserved", 0); this->add_element(113, "Reserved", 0); this->add_element(114, "Reserved", 0); this->add_element(115, "Reserved", 0); this->add_element(116, "Reserved", 0); this->add_element(117, "Reserved", 0); this->add_element(118, "Reserved", 0); this->add_element(119, "Reserved", 0); this->add_element(120, "Reserved", 0); this->add_element(121, "Reserved", 0); this->add_element(122, "Reserved", 0); this->add_element(123, "Reserved", 0); this->add_element(124, "Reserved", 0); this->add_element(125, "Reserved", 0); this->add_element(126, "Reserved", 0); this->add_element(127, "Reserved", 0); this->add_element(128, "bgpNextAdjacentAsNumber", 4); this->add_element(129, "bgpPrevAdjacentAsNumber", 4); this->add_element(130, "exporterIPv4Address", 4); this->add_element(131, "exporterIPv6Address", 16); this->add_element(132, "droppedOctetDeltaCount", 8); this->add_element(133, "droppedPacketDeltaCount", 8); this->add_element(134, "droppedOctetTotalCount", 8); this->add_element(135, "droppedPacketTotalCount", 8); this->add_element(136, "flowEndReason", 1); this->add_element(137, "commonPropertiesId", 8); this->add_element(138, "observationPointId", 8); this->add_element(139, "icmpTypeCodeIPv6", 2); this->add_element(140, "mplsTopLabelIPv6Address", 16); this->add_element(141, "lineCardId", 4); this->add_element(142, "portId", 4); this->add_element(143, "meteringProcessId", 4); this->add_element(144, "exportingProcessId", 4); this->add_element(145, "templateId", 2); this->add_element(146, "wlanChannelId", 1); this->add_element(147, "wlanSSID", 0); this->add_element(148, "flowId", 8); this->add_element(149, "observationDomainId", 4); this->add_element(150, "flowStartSeconds", 0); this->add_element(151, "flowEndSeconds", 0); this->add_element(152, "flowStartMilliseconds", 0); this->add_element(153, "flowEndMilliseconds", 0); this->add_element(154, "flowStartMicroseconds", 0); this->add_element(155, "flowEndMicroseconds", 0); this->add_element(156, "flowStartNanoseconds", 0); this->add_element(157, "flowEndNanoseconds", 0); this->add_element(158, "flowStartDeltaMicroseconds", 4); this->add_element(159, "flowEndDeltaMicroseconds", 4); this->add_element(160, "systemInitTimeMilliseconds", 0); this->add_element(161, "flowDurationMilliseconds", 4); this->add_element(162, "flowDurationMicroseconds", 4); this->add_element(163, "observedFlowTotalCount", 8); this->add_element(164, "ignoredPacketTotalCount", 8); this->add_element(165, "ignoredOctetTotalCount", 8); this->add_element(166, "notSentFlowTotalCount", 8); this->add_element(167, "notSentPacketTotalCount", 8); this->add_element(168, "notSentOctetTotalCount", 8); this->add_element(169, "destinationIPv6Prefix", 16); this->add_element(170, "sourceIPv6Prefix", 16); this->add_element(171, "postOctetTotalCount", 8); this->add_element(172, "postPacketTotalCount", 8); this->add_element(173, "flowKeyIndicator", 8); this->add_element(174, "postMCastPacketTotalCount", 8); this->add_element(175, "postMCastOctetTotalCount", 8); this->add_element(176, "icmpTypeIPv4", 1); this->add_element(177, "icmpCodeIPv4", 1); this->add_element(178, "icmpTypeIPv6", 1); this->add_element(179, "icmpCodeIPv6", 1); this->add_element(180, "udpSourcePort", 2); this->add_element(181, "udpDestinationPort", 2); this->add_element(182, "tcpSourcePort", 2); this->add_element(183, "tcpDestinationPort", 2); this->add_element(184, "tcpSequenceNumber", 4); this->add_element(185, "tcpAcknowledgementNumber", 4); this->add_element(186, "tcpWindowSize", 2); this->add_element(187, "tcpUrgentPointer", 2); this->add_element(188, "tcpHeaderLength", 1); this->add_element(189, "ipHeaderLength", 1); this->add_element(190, "totalLengthIPv4", 2); this->add_element(191, "payloadLengthIPv6", 2); this->add_element(192, "ipTTL", 1); this->add_element(193, "nextHeaderIPv6", 1); this->add_element(194, "mplsPayloadLength", 4); this->add_element(195, "ipDiffServCodePoint", 1); this->add_element(196, "ipPrecedence", 1); this->add_element(197, "fragmentFlags", 1); this->add_element(198, "octetDeltaSumOfSquares", 8); this->add_element(199, "octetTotalSumOfSquares", 8); this->add_element(200, "mplsTopLabelTTL", 1); this->add_element(201, "mplsLabelStackLength", 4); this->add_element(202, "mplsLabelStackDepth", 4); this->add_element(203, "mplsTopLabelExp", 1); this->add_element(204, "ipPayloadLength", 4); this->add_element(205, "udpMessageLength", 2); this->add_element(206, "isMulticast", 1); this->add_element(207, "ipv4IHL", 1); this->add_element(208, "ipv4Options", 4); this->add_element(209, "tcpOptions", 8); this->add_element(210, "paddingOctets", 0); this->add_element(211, "collectorIPv4Address", 4); this->add_element(212, "collectorIPv6Address", 16); this->add_element(213, "exportInterface", 4); this->add_element(214, "exportProtocolVersion", 1); this->add_element(215, "exportTransportProtocol", 1); this->add_element(216, "collectorTransportPort", 2); this->add_element(217, "exporterTransportPort", 2); this->add_element(218, "tcpSynTotalCount", 8); this->add_element(219, "tcpFinTotalCount", 8); this->add_element(220, "tcpRstTotalCount", 8); this->add_element(221, "tcpPshTotalCount", 8); this->add_element(222, "tcpAckTotalCount", 8); this->add_element(223, "tcpUrgTotalCount", 8); this->add_element(224, "ipTotalLength", 8); this->add_element(225, "postNATSourceIPv4Address", 4); this->add_element(226, "postNATDestinationIPv4Address", 4); this->add_element(227, "postNAPTSourceTransportPort", 2); this->add_element(228, "postNAPTDestinationTransportPort", 2); this->add_element(229, "natOriginatingAddressRealm", 1); this->add_element(230, "natEvent", 1); this->add_element(231, "initiatorOctets", 8); this->add_element(232, "responderOctets", 8); this->add_element(233, "firewallEvent", 1); this->add_element(234, "ingressVRFID", 4); this->add_element(235, "egressVRFID", 4); this->add_element(236, "VRFname", 0); this->add_element(237, "postMplsTopLabelExp", 1); this->add_element(238, "tcpWindowScale", 2); this->add_element(239, "biflowDirection", 1); this->add_element(240, "ethernetHeaderLength", 1); this->add_element(241, "ethernetPayloadLength", 2); this->add_element(242, "ethernetTotalLength", 2); this->add_element(243, "dot1qVlanId", 2); this->add_element(244, "dot1qPriority", 1); this->add_element(245, "dot1qCustomerVlanId", 2); this->add_element(246, "dot1qCustomerPriority", 1); this->add_element(247, "metroEvcId", 0); this->add_element(248, "metroEvcType", 1); this->add_element(249, "pseudoWireId", 4); this->add_element(250, "pseudoWireType", 2); this->add_element(251, "pseudoWireControlWord", 4); this->add_element(252, "ingressPhysicalInterface", 4); this->add_element(253, "egressPhysicalInterface", 4); this->add_element(254, "postDot1qVlanId", 2); this->add_element(255, "postDot1qCustomerVlanId", 2); this->add_element(256, "ethernetType", 2); this->add_element(257, "postIpPrecedence", 1); this->add_element(258, "collectionTimeMilliseconds", 0); this->add_element(259, "exportSctpStreamId", 2); this->add_element(260, "maxExportSeconds", 0); this->add_element(261, "maxFlowEndSeconds", 0); this->add_element(262, "messageMD5Checksum", 0); this->add_element(263, "messageScope", 1); this->add_element(264, "minExportSeconds", 0); this->add_element(265, "minFlowStartSeconds", 0); this->add_element(266, "opaqueOctets", 0); this->add_element(267, "sessionScope", 1); this->add_element(268, "maxFlowEndMicroseconds", 0); this->add_element(269, "maxFlowEndMilliseconds", 0); this->add_element(270, "maxFlowEndNanoseconds", 0); this->add_element(271, "minFlowStartMicroseconds", 0); this->add_element(272, "minFlowStartMilliseconds", 0); this->add_element(273, "minFlowStartNanoseconds", 0); this->add_element(274, "collectorCertificate", 0); this->add_element(275, "exporterCertificate", 0); this->add_element(276, "dataRecordsReliability", 0); this->add_element(277, "observationPointType", 1); this->add_element(278, "newConnectionDeltaCount", 4); this->add_element(279, "connectionSumDurationSeconds", 8); this->add_element(280, "connectionTransactionId", 8); this->add_element(281, "postNATSourceIPv6Address", 16); this->add_element(282, "postNATDestinationIPv6Address", 16); this->add_element(283, "natPoolId", 4); this->add_element(284, "natPoolName", 0); this->add_element(285, "anonymizationFlags", 2); this->add_element(286, "anonymizationTechnique", 2); this->add_element(287, "informationElementIndex", 2); this->add_element(288, "p2pTechnology", 0); this->add_element(289, "tunnelTechnology", 0); this->add_element(290, "encryptedTechnology", 0); this->add_element(291, "basicList", 0); this->add_element(292, "subTemplateList", 0); this->add_element(293, "subTemplateMultiList", 0); this->add_element(294, "bgpValidityState", 1); this->add_element(295, "IPSecSPI", 4); this->add_element(296, "greKey", 4); this->add_element(297, "natType", 1); this->add_element(298, "initiatorPackets", 8); this->add_element(299, "responderPackets", 8); this->add_element(300, "observationDomainName", 0); this->add_element(301, "selectionSequenceId", 8); this->add_element(302, "selectorId", 8); this->add_element(303, "informationElementId", 2); this->add_element(304, "selectorAlgorithm", 2); this->add_element(305, "samplingPacketInterval", 4); this->add_element(306, "samplingPacketSpace", 4); this->add_element(307, "samplingTimeInterval", 4); this->add_element(308, "samplingTimeSpace", 4); this->add_element(309, "samplingSize", 4); this->add_element(310, "samplingPopulation", 4); this->add_element(311, "samplingProbability", 0); this->add_element(312, "dataLinkFrameSize", 2); this->add_element(313, "ipHeaderPacketSection", 0); this->add_element(314, "ipPayloadPacketSection", 0); this->add_element(315, "dataLinkFrameSection", 0); this->add_element(316, "mplsLabelStackSection", 0); this->add_element(317, "mplsPayloadPacketSection", 0); this->add_element(318, "selectorIdTotalPktsObserved", 8); this->add_element(319, "selectorIdTotalPktsSelected", 8); this->add_element(320, "absoluteError", 0); this->add_element(321, "relativeError", 0); this->add_element(322, "observationTimeSeconds", 0); this->add_element(323, "observationTimeMilliseconds", 0); this->add_element(324, "observationTimeMicroseconds", 0); this->add_element(325, "observationTimeNanoseconds", 0); this->add_element(326, "digestHashValue", 8); this->add_element(327, "hashIPPayloadOffset", 8); this->add_element(328, "hashIPPayloadSize", 8); this->add_element(329, "hashOutputRangeMin", 8); this->add_element(330, "hashOutputRangeMax", 8); this->add_element(331, "hashSelectedRangeMin", 8); this->add_element(332, "hashSelectedRangeMax", 8); this->add_element(333, "hashDigestOutput", 0); this->add_element(334, "hashInitialiserValue", 8); this->add_element(335, "selectorName", 0); this->add_element(336, "upperCILimit", 0); this->add_element(337, "lowerCILimit", 0); this->add_element(338, "confidenceLevel", 0); this->add_element(339, "informationElementDataType", 1); this->add_element(340, "informationElementDescription", 0); this->add_element(341, "informationElementName", 0); this->add_element(342, "informationElementRangeBegin", 8); this->add_element(343, "informationElementRangeEnd", 8); this->add_element(344, "informationElementSemantics", 1); this->add_element(345, "informationElementUnits", 2); this->add_element(346, "privateEnterpriseNumber", 4); this->add_element(347, "virtualStationInterfaceId", 0); this->add_element(348, "virtualStationInterfaceName", 0); this->add_element(349, "virtualStationUUID", 0); this->add_element(350, "virtualStationName", 0); this->add_element(351, "layer2SegmentId", 8); this->add_element(352, "layer2OctetDeltaCount", 8); this->add_element(353, "layer2OctetTotalCount", 8); this->add_element(354, "ingressUnicastPacketTotalCount", 8); this->add_element(355, "ingressMulticastPacketTotalCount", 8); this->add_element(356, "ingressBroadcastPacketTotalCount", 8); this->add_element(357, "egressUnicastPacketTotalCount", 8); this->add_element(358, "egressBroadcastPacketTotalCount", 8); this->add_element(359, "monitoringIntervalStartMilliSeconds", 0); this->add_element(360, "monitoringIntervalEndMilliSeconds", 0); this->add_element(361, "portRangeStart", 2); this->add_element(362, "portRangeEnd", 2); this->add_element(363, "portRangeStepSize", 2); this->add_element(364, "portRangeNumPorts", 2); this->add_element(365, "staMacAddress", 0); this->add_element(366, "staIPv4Address", 4); this->add_element(367, "wtpMacAddress", 0); this->add_element(368, "ingressInterfaceType", 4); this->add_element(369, "egressInterfaceType", 4); this->add_element(370, "rtpSequenceNumber", 2); this->add_element(371, "userName", 0); this->add_element(372, "applicationCategoryName", 0); this->add_element(373, "applicationSubCategoryName", 0); this->add_element(374, "applicationGroupName", 0); this->add_element(375, "originalFlowsPresent", 8); this->add_element(376, "originalFlowsInitiated", 8); this->add_element(377, "originalFlowsCompleted", 8); this->add_element(378, "distinctCountOfSourceIPAddress", 8); this->add_element(379, "distinctCountOfDestinationIPAddress", 8); this->add_element(380, "distinctCountOfSourceIPv4Address", 4); this->add_element(381, "distinctCountOfDestinationIPv4Address", 4); this->add_element(382, "distinctCountOfSourceIPv6Address", 8); this->add_element(383, "distinctCountOfDestinationIPv6Address", 8); this->add_element(384, "valueDistributionMethod", 1); this->add_element(385, "rfc3550JitterMilliseconds", 4); this->add_element(386, "rfc3550JitterMicroseconds", 4); this->add_element(387, "rfc3550JitterNanoseconds", 4); this->add_element(388, "dot1qDEI", 0); this->add_element(389, "dot1qCustomerDEI", 0); this->add_element(390, "flowSelectorAlgorithm", 2); this->add_element(391, "flowSelectedOctetDeltaCount", 8); this->add_element(392, "flowSelectedPacketDeltaCount", 8); this->add_element(393, "flowSelectedFlowDeltaCount", 8); this->add_element(394, "selectorIDTotalFlowsObserved", 8); this->add_element(395, "selectorIDTotalFlowsSelected", 8); this->add_element(396, "samplingFlowInterval", 8); this->add_element(397, "samplingFlowSpacing", 8); this->add_element(398, "flowSamplingTimeInterval", 8); this->add_element(399, "flowSamplingTimeSpacing", 8); this->add_element(400, "hashFlowDomain", 2); this->add_element(401, "transportOctetDeltaCount", 8); this->add_element(402, "transportPacketDeltaCount", 8); this->add_element(403, "originalExporterIPv4Address", 4); this->add_element(404, "originalExporterIPv6Address", 16); this->add_element(405, "originalObservationDomainId", 4); this->add_element(406, "intermediateProcessId", 4); this->add_element(407, "ignoredDataRecordTotalCount", 8); this->add_element(408, "dataLinkFrameType", 2); this->add_element(409, "sectionOffset", 2); this->add_element(410, "sectionExportedOctets", 2); this->add_element(411, "dot1qServiceInstanceTag", 0); this->add_element(412, "dot1qServiceInstanceId", 4); this->add_element(413, "dot1qServiceInstancePriority", 1); this->add_element(414, "dot1qCustomerSourceMacAddress", 0); this->add_element(415, "dot1qCustomerDestinationMacAddress", 0); this->add_element(416, "reserved", 0); this->add_element(417, "postLayer2OctetDeltaCount", 8); this->add_element(418, "postMCastLayer2OctetDeltaCount", 8); this->add_element(419, "reserved", 0); this->add_element(420, "postLayer2OctetTotalCount", 8); this->add_element(421, "postMCastLayer2OctetTotalCount", 8); this->add_element(422, "minimumLayer2TotalLength", 8); this->add_element(423, "maximumLayer2TotalLength", 8); this->add_element(424, "droppedLayer2OctetDeltaCount", 8); this->add_element(425, "droppedLayer2OctetTotalCount", 8); this->add_element(426, "ignoredLayer2OctetTotalCount", 8); this->add_element(427, "notSentLayer2OctetTotalCount", 8); this->add_element(428, "layer2OctetDeltaSumOfSquares", 8); this->add_element(429, "layer2OctetTotalSumOfSquares", 8); this->add_element(430, "layer2FrameDeltaCount", 8); this->add_element(431, "layer2FrameTotalCount", 8); this->add_element(432, "pseudoWireDestinationIPv4Address", 4); this->add_element(433, "ignoredLayer2FrameTotalCount", 8); this->add_element(434, "mibObjectValueInteger", 4); this->add_element(435, "mibObjectValueOctetString", 0); this->add_element(436, "mibObjectValueOID", 0); this->add_element(437, "mibObjectValueBits", 0); this->add_element(438, "mibObjectValueIPAddress", 4); this->add_element(439, "mibObjectValueCounter", 8); this->add_element(440, "mibObjectValueGauge", 4); this->add_element(441, "mibObjectValueTimeTicks", 4); this->add_element(442, "mibObjectValueUnsigned", 4); this->add_element(443, "mibObjectValueTable", 0); this->add_element(444, "mibObjectValueRow", 0); this->add_element(445, "mibObjectIdentifier", 0); this->add_element(446, "mibSubIdentifier", 4); this->add_element(447, "mibIndexIndicator", 8); this->add_element(448, "mibCaptureTimeSemantics", 1); this->add_element(449, "mibContextEngineID", 0); this->add_element(450, "mibContextName", 0); this->add_element(451, "mibObjectName", 0); this->add_element(452, "mibObjectDescription", 0); this->add_element(453, "mibObjectSyntax", 0); this->add_element(454, "mibModuleName", 0); this->add_element(455, "mobileIMSI", 0); this->add_element(456, "mobileMSISDN", 0); this->add_element(457, "httpStatusCode", 2); this->add_element(458, "sourceTransportPortsLimit", 2); this->add_element(459, "httpRequestMethod", 0); this->add_element(460, "httpRequestHost", 0); this->add_element(461, "httpRequestTarget", 0); this->add_element(462, "httpMessageVersion", 0); this->add_element(463, "natInstanceID", 4); this->add_element(464, "internalAddressRealm", 0); this->add_element(465, "externalAddressRealm", 0); this->add_element(466, "natQuotaExceededEvent", 4); this->add_element(467, "natThresholdEvent", 4); this->add_element(468, "httpUserAgent", 0); this->add_element(469, "httpContentType", 0); this->add_element(470, "httpReasonPhrase", 0); this->add_element(471, "maxSessionEntries", 4); this->add_element(472, "maxBIBEntries", 4); this->add_element(473, "maxEntriesPerUser", 4); this->add_element(474, "maxSubscribers", 4); this->add_element(475, "maxFragmentsPendingReassembly", 4); this->add_element(476, "addressPoolHighThreshold", 4); this->add_element(477, "addressPoolLowThreshold", 4); this->add_element(478, "addressPortMappingHighThreshold", 4); this->add_element(479, "addressPortMappingLowThreshold", 4); this->add_element(480, "addressPortMappingPerUserHighThreshold", 4); this->add_element(481, "globalAddressMappingHighThreshold", 4); this->add_element(482, "vpnIdentifier", 0); this->add_element(483, "bgpCommunity", 4); this->add_element(484, "bgpSourceCommunityList", 0); this->add_element(485, "bgpDestinationCommunityList", 0); this->add_element(486, "bgpExtendedCommunity", 0); this->add_element(487, "bgpSourceExtendedCommunityList", 0); this->add_element(488, "bgpDestinationExtendedCommunityList", 0); this->add_element(489, "bgpLargeCommunity", 0); this->add_element(490, "bgpSourceLargeCommunityList", 0); this->add_element(491, "bgpDestinationLargeCommunityList", 0); this->add_element(492, "srhFlagsIPv6", 1); this->add_element(493, "srhTagIPv6", 2); this->add_element(494, "srhSegmentIPv6", 16); this->add_element(495, "srhActiveSegmentIPv6", 16); this->add_element(496, "srhSegmentIPv6BasicList", 0); this->add_element(497, "srhSegmentIPv6ListSection", 0); this->add_element(498, "srhSegmentsIPv6Left", 1); this->add_element(499, "srhIPv6Section", 0); this->add_element(500, "srhIPv6ActiveSegmentType", 1); this->add_element(501, "srhSegmentIPv6LocatorLength", 1); this->add_element(502, "srhSegmentIPv6EndpointBehavior", 2); } pavel-odintsov-fastnetmon-394fbe0/src/ipfix_fields/ipfix_rfc.hpp000066400000000000000000000015121520703010000251570ustar00rootroot00000000000000#pragma once /* This file is autogenerated with script ipfix_csv_processor.pl */ /* Please do not edit it directly */ #include #include class ipfix_information_element_t { public: ipfix_information_element_t(std::string name, unsigned int length); ipfix_information_element_t(); std::string get_name(); unsigned int get_length(); std::string name; unsigned int length; }; typedef std::map ipfix_database_t; class ipfix_information_database { public: ipfix_information_database(); bool add_element(unsigned int field_id, std::string name, unsigned int length); std::string get_name_by_id(unsigned int field_id); unsigned int get_length_by_id(unsigned int field_id); private: ipfix_database_t database; }; pavel-odintsov-fastnetmon-394fbe0/src/irq_balance_manually.sh000077500000000000000000000010641520703010000245330ustar00rootroot00000000000000#!/bin/bash # from http://habrahabr.ru/post/108240/ ncpus=`grep -ciw ^processor /proc/cpuinfo` test "$ncpus" -gt 1 || exit 1 n=0 for irq in `cat /proc/interrupts | grep eth | awk '{print $1}' | sed s/\://g` do f="/proc/irq/$irq/smp_affinity" test -r "$f" || continue cpu=$[$ncpus - ($n % $ncpus) - 1] if [ $cpu -ge 0 ] then mask=`printf %x $[2 ** $cpu]` echo "Assign SMP affinity: eth queue $n, irq $irq, cpu $cpu, mask 0x$mask" echo "$mask" > "$f" let n+=1 fi done pavel-odintsov-fastnetmon-394fbe0/src/juniper_plugin/000077500000000000000000000000001520703010000230635ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/juniper_plugin/README.md000066400000000000000000000026331520703010000243460ustar00rootroot00000000000000Juniper FastNetMon plug-in =========== Overview -------- Connects to a Juniper router and adds or removes a blackhole rule for an attack by IP address. The actions can be modified such as adding a firewall rule. This script uses the Juniper NETCONF PHP API. More information about this can be found at the following URL: * https://github.com/Juniper/netconf-php Installation ------------ #### Prerequisite You must have a user and netconf enabled on your Juniper to enable netconf go to your cli and type: ``` user@host> configure user@host# set netconf ssh ``` if you wish to change netconf port instead of ``` user@host# set netconf ssh ``` use ``` user@host# set netconf ssh port ``` Install php to your server: ``` sudo apt-get install php-cli php ``` #### Process 1. Configure the router in the ```fastnetmon_juniper.php``` file ``` $cfg['hostname'] = "10.0.0.1"; // Juniper IP $cfg['port'] = 880; //NETCONF Port $cfg['username'] = "user"; //user $cfg['password'] = "password"; //pass ``` 2. Change the ```notify_about_attack.sh``` with the new to run the PHP script This is the first buggy version, you are welcome to add more features. 3. Set executable bit ```sudo chmod +x /etc/fastnetmon/scripts/notify_about_attack.sh``` Changelog --------- v1.0 - 5 Dec 18 - Initial version Author: Christian David Based on Mikrotik Plugin by Maximiliano Dobladez pavel-odintsov-fastnetmon-394fbe0/src/juniper_plugin/fastnetmon_juniper.php000066400000000000000000000137751520703010000275230ustar00rootroot00000000000000#!/usr/bin/php * * Credits for the Netconf API By Juniper/netconf-php * Script based on Mikrotik Plugin by Maximiliano Dobladez * * Made based on a MX5 CLI and not tested yet, please feedback-us in Issues on github * * LICENSE: GPLv2 GNU GENERAL PUBLIC LICENSE * * * v1.0 - 5 Dec 18 - initial version ******************************/ error_reporting(E_ALL); ini_set('display_errors', 'On'); define("_VER", '1.0'); /* NOTE: YOU NEED TO ENABLE NETCONF ON YOUR JUNIPER */ /* https://www.juniper.net/documentation/en_US/junos/topics/task/configuration/netconf-ssh-connection-establishing.html#task-netconf-service-over-ssh-enabling */ /* Example configuration file /etc/juniper_integration.json: { "hostname": "10.0.0.1", "port": 880, "username": "user", "password": "password" } */ $config_path = "/etc/juniper_integration.json"; if (!file_exists($config_path)) { $msg = "Configuration file not found: " . $config_path; _log($msg); echo $msg . "\n"; exit(1); } $cfg = json_decode(file_get_contents($config_path), true); if (!is_array($cfg)) { $msg = "Failed to parse configuration file: " . $config_path; _log($msg); echo $msg . "\n"; exit(1); } foreach (['hostname', 'port', 'username', 'password'] as $key) { if (empty($cfg[$key])) { $msg = "Missing required config key: " . $key; _log($msg); echo $msg . "\n"; exit(1); } } // help if ($argc > 1 && $argv[1] == "help") { $msg = "Juniper API Integration for FastNetMon - Ver: " . _VER; echo $msg; exit(1); } /* This script will get following params from FastNetMon: $1 client_ip_as_string $2 data_direction $3 pps_as_string $4 action (ban or unban) */ // Ensure that we got all required arguments if ($argc <= 4) { $msg = "Juniper API Integration for FastNetMon - Ver: " . _VER . "\n"; $msg .= "missing arguments\n"; $msg .= "php fastnetmon_juniper.php [IP] [data_direction] [pps_as_string] [action]\n"; _log($msg); echo $msg; exit(1); } // IPv4 or IPv6 address of attack $IP_ATTACK = $argv[1]; if (filter_var($IP_ATTACK, FILTER_VALIDATE_IP) === false) { $msg = "Invalid IP address: " . $IP_ATTACK; _log($msg); echo $msg . "\n"; exit(1); } // incoming, outgoing or unknown $DIRECTION_ATTACK = $argv[2]; if (!in_array($DIRECTION_ATTACK, ['incoming', 'outgoing', 'unknown'], true)) { $msg = "Invalid direction: " . $DIRECTION_ATTACK . ". Must be incoming, outgoing, or unknown."; _log($msg); echo $msg . "\n"; exit(1); } // Power of attack in packets per second $POWER_ATTACK = $argv[3]; if (!ctype_digit($POWER_ATTACK)) { $msg = "Invalid pps value: " . $POWER_ATTACK . ". Must be a positive integer."; _log($msg); echo $msg . "\n"; exit(1); } // Action: ban or unban $ACTION_ATTACK = $argv[4]; if (!in_array($ACTION_ATTACK, ['ban', 'unban'], true)) { $msg = "Invalid action: " . $ACTION_ATTACK . ". Must be ban or unban."; _log($msg); echo $msg . "\n"; exit(1); } $netconf_path = __DIR__ . "/netconf/netconf/Device.php"; if (!file_exists($netconf_path)) { $msg = "Netconf library not found: " . $netconf_path; _log($msg); echo $msg . "\n"; exit(1); } if (!function_exists('expect_popen')) { $msg = "PHP expect extension is required but not installed. Install it with PECL: pecl install expect"; _log($msg); echo $msg . "\n"; exit(1); } set_include_path(get_include_path() . PATH_SEPARATOR . dirname($netconf_path)); require_once $netconf_path; $conn = new Device($cfg); $time_now = date("Y-m-d H:i:s", time()); if ($ACTION_ATTACK == 'ban') { try { $desc = 'FastNetMon Community: IP ' . $IP_ATTACK . ' blocked because ' . $DIRECTION_ATTACK . ' attack with power ' . $POWER_ATTACK . ' pps | at ' . $time_now; $conn->connect(); $locked = $conn->lock_config(); if ($locked) { // Community 65535:666 = BLACKHOLE $conn->load_set_configuration("set routing-options static route {$IP_ATTACK} community 65535:666 discard"); $conn->commit(); } $conn->unlock(); $conn->close(); _log($desc); echo $desc . "\n"; } catch (NetconfException $e) { $msg = "Couldn't connect to " . $cfg['hostname'] . " - " . $e->getMessage(); _log($msg); echo $msg . "\n"; exit(1); } catch (Exception $e) { $msg = "Ban failed for " . $IP_ATTACK . " - " . $e->getMessage(); _log($msg); echo $msg . "\n"; exit(1); } } elseif ($ACTION_ATTACK == 'unban') { try { $desc = 'FastNetMon Community: IP ' . $IP_ATTACK . ' remove from blacklist.'; $conn->connect(); $locked = $conn->lock_config(); if ($locked) { $conn->load_set_configuration("delete routing-options static route {$IP_ATTACK}/32"); $conn->commit(); } $conn->unlock(); $conn->close(); _log($desc); echo $desc . "\n"; } catch (NetconfException $e) { $msg = "Couldn't connect to " . $cfg['hostname'] . " - " . $e->getMessage(); _log($msg); echo $msg . "\n"; exit(1); } catch (Exception $e) { $msg = "Unban failed for " . $IP_ATTACK . " - " . $e->getMessage(); _log($msg); echo $msg . "\n"; exit(1); } } else { $msg = "Unknown action: " . $ACTION_ATTACK; _log($msg); echo $msg . "\n"; exit(1); } /** * [_log Write a log file] * @param string $msg text to log */ function _log(string $msg): void { $FILE_LOG_TMP = "/tmp/fastnetmon_api_juniper.log"; $line = date("D M j H:i:s T Y") . " - [FASTNETMON] - " . $msg . "\n"; file_put_contents($FILE_LOG_TMP, $line, FILE_APPEND); } ?>pavel-odintsov-fastnetmon-394fbe0/src/juniper_plugin/netconf/000077500000000000000000000000001520703010000245175ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/juniper_plugin/notify_about_attack.sh000077500000000000000000000006321520703010000274540ustar00rootroot00000000000000#!/usr/bin/env bash # # Fastnetmon: Juniper plugin # # Author: - info@mkesolutions.net - http://maxid.com.ar # Modified by Christian David for juniper implementation # # This script will get following params: # $1 client_ip_as_string # $2 data_direction # $3 pps_as_string # $4 action (ban or unban) php -f /opt/fastnetmon/fastnetmon_juniper.php $1 $2 $3 $4 exit 0 pavel-odintsov-fastnetmon-394fbe0/src/libpatricia/000077500000000000000000000000001520703010000223145ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/libpatricia/copyright000066400000000000000000000023451520703010000242530ustar00rootroot00000000000000$Id: COPYRIGHT,v 1.1.1.1 2013/08/15 18:46:09 labovit Exp $ Copyright (c) 1999-2013 The Regents of the University of Michigan ("The Regents") and Merit Network, Inc. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.pavel-odintsov-fastnetmon-394fbe0/src/libpatricia/credits.txt000066400000000000000000000054351520703010000245210ustar00rootroot00000000000000 [newtool.gif] MRT Credits The Multi-Threaded Routing Toolkit _________________________________________________________________ MRT was developed by [1]Merit Network, Inc., under National Science Foundation grant NCR-9318902, "Experimentation with Routing Technology to be Used for Inter-Domain Routing in the Internet." Current MRT Staff * [2]Craig Labovitz * [3]Makaki Hirabaru * [4]Farnam Jahanian * Susan Hares * Susan R. Harris * Nathan Binkert * Gerald Winters Project Alumni * [5]Marc Unangst * John Scudder The BGP4+ extension was originally written by Francis Dupont . The public domain Struct C-library of linked list, hash table and memory allocation routines was developed by Jonathan Dekock . Susan Rebecca Harris provided help with the documentation. David Ward provided bug fixes and helpful suggestions. Some sections of code and architecture ideas were taken from the GateD routing daemon. The first port to Linux with IPv6 was done by Pedro Roque . Some interface routines to the Linux kernel were originally written by him. Alexey Kuznetsov made enhancements to 1.4.3a and fixed the Linux kernel intarface. Linux's netlink interface was written, referring to his code "iproute2". We would also like to thank our other colleagues in Japan, Portugal, the Netherlands, the UK, and the US for their many contributions to the MRT development effort. _________________________________________________________________ Cisco is a registered trademark of Cisco Systems Inc. _________________________________________________________________ Merit Network 4251 Plymouth Road Suite C Ann Arbor, MI 48105-2785 734-764-9430 info@merit.edu _________________________________________________________________ 1999 Merit Network, Inc. [6]www@merit.edu References 1. http://www.merit.edu/ 2. http://www.merit.edu/~labovit 3. http://www.merit.edu/~masaki 4. http://www.eecs.umich.edu/~farnam 5. http://www.contrib.andrew.cmu.edu/~mju/ 6. mailto:www@merit.edu pavel-odintsov-fastnetmon-394fbe0/src/libpatricia/patricia.cpp000066400000000000000000000471471520703010000246310ustar00rootroot00000000000000/* * $Id: patricia.c,v 1.7 2005/12/07 20:46:41 dplonka Exp $ * Dave Plonka * * This product includes software developed by the University of Michigan, * Merit Network, Inc., and their contributors. * * This file had been called "radix.c" in the MRT sources. * * I renamed it to "patricia.c" since it's not an implementation of a general * radix trie. Also I pulled in various requirements from "prefix.c" and * "demo.c" so that it could be used as a standalone API. */ // Actual link to MRT project located here: https://github.com/deepfield/MRT #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" static char copyright[] = "This product includes software developed by the University of Michigan, Merit" "Network, Inc., and their contributors."; #pragma GCC diagnostic pop #ifdef _WIN32 #include #include // for inet_ntop #else // for inet_addr #include // for inet_addr #endif #include /* assert */ #include /* isdigit */ #include /* errno */ #include /* sprintf, fprintf, stderr */ #include /* free, atol, calloc */ #include /* memcpy, strchr, strlen */ #include "patricia.hpp" #define PATRICIA_MAXBITS (sizeof(struct in6_addr) * 8) #define prefix_touchar(prefix) ((u_char*)&(prefix)->add.sin) #define MAXLINE 1024 #define BIT_TEST(f, b) ((f) & (b)) /* prefix_tochar convert prefix information to bytes */ u_char* prefix_tochar(prefix_t* prefix) { if (prefix == NULL) { return NULL; } return ((u_char*)&prefix->add.sin); } int comp_with_mask(void* addr, void* dest, u_int mask) { if (/* mask/8 == 0 || */ memcmp(addr, dest, mask / 8) == 0) { int n = mask / 8; int m = ((-1) << (8 - (mask % 8))); if (mask % 8 == 0 || (((u_char*)addr)[n] & m) == (((u_char*)dest)[n] & m)) { return 1; } } return 0; } /* this allows imcomplete prefix */ int my_inet_pton(int af, const char* src, void* dst) { if (af == AF_INET) { int i = 0; int c = 0; int val = 0; u_char xp[sizeof(struct in_addr)] = { 0, 0, 0, 0 }; for (i = 0;; i++) { c = *src++; if (!isdigit(c)) { return -1; } val = 0; do { val = val * 10 + c - '0'; if (val > 255) { return 0; } c = *src++; } while (c && isdigit(c)); xp[i] = val; if (c == '\0') break; if (c != '.') { return 0; } if (i >= 3) { return 0; } } memcpy(dst, xp, sizeof(struct in_addr)); return 1; } else if (af == AF_INET6) { return inet_pton(af, src, dst); } else { errno = EAFNOSUPPORT; return -1; } } #define PATRICIA_MAX_THREADS 16 /* * convert prefix information to ascii string with length * thread safe and (almost) re-entrant implementation */ char* prefix_toa2x(prefix_t* prefix, char* buff, int with_len) { if (prefix == NULL) { return (char*)"(Null)"; } assert(prefix->ref_count >= 0); if (buff == NULL) { struct buffer { char buffs[PATRICIA_MAX_THREADS][48 + 5]; u_int i; } * buffp; { /* for scope only */ static struct buffer local_buff; buffp = &local_buff; } if (buffp == NULL) { /* XXX should we report an error? */ return NULL; } buff = buffp->buffs[buffp->i++ % PATRICIA_MAX_THREADS]; } if (prefix->family == AF_INET) { assert(prefix->bitlen <= sizeof(struct in_addr) * 8); u_char* a = prefix_touchar(prefix); if (with_len) { sprintf(buff, "%d.%d.%d.%d/%d", a[0], a[1], a[2], a[3], prefix->bitlen); } else { sprintf(buff, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]); } return (buff); } else if (prefix->family == AF_INET6) { char* r = (char*)inet_ntop(AF_INET6, &prefix->add.sin6, buff, 48 /* a guess value */); if (r && with_len) { assert(prefix->bitlen <= sizeof(struct in6_addr) * 8); sprintf(buff + strlen(buff), "/%d", prefix->bitlen); } return buff; } else { return NULL; } } /* prefix_toa2 * convert prefix information to ascii string */ char* prefix_toa2(prefix_t* prefix, char* buff) { return prefix_toa2x(prefix, buff, 0); } /* prefix_toa */ char* prefix_toa(prefix_t* prefix) { return prefix_toa2(prefix, (char*)NULL); } prefix_t* New_Prefix2(int family, void* dest, int bitlen, prefix_t* prefix) { int dynamic_allocated = 0; int default_bitlen = sizeof(struct in_addr) * 8; if (family == AF_INET6) { default_bitlen = sizeof(struct in6_addr) * 8; if (prefix == NULL) { prefix = (prefix_t*)calloc(1, sizeof(prefix_t)); dynamic_allocated++; } memcpy(&prefix->add.sin6, dest, sizeof(struct in6_addr)); } else if (family == AF_INET) { if (prefix == NULL) { prefix = (prefix_t*)calloc(1, sizeof(prefix4_t)); dynamic_allocated++; } memcpy(&prefix->add.sin, dest, sizeof(struct in_addr)); } else { return NULL; } prefix->bitlen = (bitlen >= 0) ? bitlen : default_bitlen; prefix->family = family; prefix->ref_count = 0; if (dynamic_allocated) { prefix->ref_count++; } /* printf("[C %s, %d]\n", prefix_toa (prefix), prefix->ref_count); */ return prefix; } prefix_t* New_Prefix(int family, void* dest, int bitlen) { return New_Prefix2(family, dest, bitlen, NULL); } // Converts string representation of prefix into out prefix_t structure prefix_t* ascii2prefix(int family, const char* string) { u_long bitlen = 0; u_long maxbitlen = 0; const char* cp = nullptr; struct in_addr sin {}; struct in6_addr sin6 {}; int result = 0; char save[MAXLINE]; if (string == NULL) { return NULL; } /* easy way to handle both families */ if (family == 0) { family = AF_INET; if (strchr(string, ':')) { family = AF_INET6; } } if (family == AF_INET) { maxbitlen = sizeof(struct in_addr) * 8; } else if (family == AF_INET6) { maxbitlen = sizeof(struct in6_addr) * 8; } if ((cp = strchr(string, '/')) != NULL) { bitlen = atol(cp + 1); /* *cp = '\0'; */ /* copy the string to save. Avoid destroying the string */ assert(cp - string < MAXLINE); memcpy(save, string, cp - string); save[cp - string] = '\0'; string = save; if (bitlen < 0 || bitlen > maxbitlen) { bitlen = maxbitlen; } } else { bitlen = maxbitlen; } if (family == AF_INET) { if ((result = my_inet_pton(AF_INET, string, &sin)) <= 0) { return NULL; } return New_Prefix(AF_INET, &sin, bitlen); } else if (family == AF_INET6) { if ((result = inet_pton(AF_INET6, string, &sin6)) <= 0) { return NULL; } return New_Prefix(AF_INET6, &sin6, bitlen); } else { return NULL; } } prefix_t* Ref_Prefix(prefix_t* prefix) { if (prefix == NULL) { return NULL; } if (prefix->ref_count == 0) { /* make a copy in case of a static prefix */ return New_Prefix2(prefix->family, &prefix->add, prefix->bitlen, NULL); } prefix->ref_count++; return prefix; } void Deref_Prefix(prefix_t* prefix) { if (prefix == NULL) { return; } /* for secure programming, raise an assert. no static prefix can call this */ assert(prefix->ref_count > 0); prefix->ref_count--; assert(prefix->ref_count >= 0); if (prefix->ref_count <= 0) { free(prefix); return; } } /* these routines support continuous mask only */ patricia_tree_t* New_Patricia(int maxbits) { patricia_tree_t* patricia = (patricia_tree_t*)calloc(1, sizeof *patricia); patricia->maxbits = maxbits; patricia->head = NULL; patricia->num_active_node = 0; assert(maxbits <= PATRICIA_MAXBITS); /* XXX */ return patricia; } // if func is supplied, it will be called as func(node->data) before deleting the node void Clear_Patricia(patricia_tree_t* patricia, std::function func) { assert(patricia); if (patricia->head) { patricia_node_t* Xstack[PATRICIA_MAXBITS + 1]; patricia_node_t** Xsp = Xstack; patricia_node_t* Xrn = patricia->head; while (Xrn) { patricia_node_t* l = Xrn->l; patricia_node_t* r = Xrn->r; if (Xrn->prefix) { Deref_Prefix(Xrn->prefix); // printf("We are near function call on nested data\n"); if (Xrn->data && func) { func(Xrn->data); } } else { assert(Xrn->data == NULL); } free(Xrn); patricia->num_active_node--; if (l) { if (r) { *Xsp++ = r; } Xrn = l; } else if (r) { Xrn = r; } else if (Xsp != Xstack) { Xrn = *(--Xsp); } else { Xrn = NULL; } } } assert(patricia->num_active_node == 0); /* free (patricia); */ } void Destroy_Patricia(patricia_tree_t* patricia, std::function func) { Clear_Patricia(patricia, func); free(patricia); } // Overload where we are not doing any actions with data payload // But please be carefeul! If you have used data field you should use extended version of function void Destroy_Patricia(patricia_tree_t* patricia) { auto function_which_do_nothing = [](void* ptr) {}; Clear_Patricia(patricia, function_which_do_nothing); free(patricia); } /* * if func is supplied, it will be called as func(node->prefix, node->data) */ void patricia_process(patricia_tree_t* patricia, std::function func) { patricia_node_t* node; assert(func); patricia_node_t* Xstack[PATRICIA_MAXBITS + 1]; patricia_node_t** Xsp = Xstack; patricia_node_t* Xrn = patricia->head; while ((node = Xrn)) { if (node->prefix) { func(node->prefix, node->data); } if (Xrn->l) { if (Xrn->r) { *Xsp++ = Xrn->r; } Xrn = Xrn->l; } else if (Xrn->r) { Xrn = Xrn->r; } else if (Xsp != Xstack) { Xrn = *(--Xsp); } else { Xrn = (patricia_node_t*)0; } } } patricia_node_t* patricia_search_exact(patricia_tree_t* patricia, prefix_t* prefix) { patricia_node_t* node; u_char* addr; u_int bitlen; assert(patricia); assert(prefix); assert(prefix->bitlen <= patricia->maxbits); if (patricia->head == NULL) { return NULL; } node = patricia->head; addr = prefix_touchar(prefix); bitlen = prefix->bitlen; while (node->bit < bitlen) { if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { node = node->r; } else { node = node->l; } if (node == NULL) { return NULL; } } if (node->bit > bitlen || node->prefix == NULL) { return NULL; } assert(node->bit == bitlen); assert(node->bit == node->prefix->bitlen); if (comp_with_mask(prefix_tochar(node->prefix), prefix_tochar(prefix), bitlen)) { // printf("patricia_search_exact: found %s/%d\n", prefix_toa(node->prefix), node->prefix->bitlen); return (node); } return NULL; } /* if inclusive != 0, "best" may be the given prefix itself */ patricia_node_t* patricia_search_best2(patricia_tree_t* patricia, prefix_t* prefix, int inclusive) { patricia_node_t* node = nullptr; patricia_node_t* stack[PATRICIA_MAXBITS + 1]; u_char* addr = nullptr; u_int bitlen = 0; int cnt = 0; assert(patricia); assert(prefix); assert(prefix->bitlen <= patricia->maxbits); if (patricia->head == NULL) return (NULL); node = patricia->head; addr = prefix_touchar(prefix); bitlen = prefix->bitlen; while (node->bit < bitlen) { if (node->prefix) { stack[cnt++] = node; } if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { node = node->r; } else { node = node->l; } if (node == NULL) break; } if (inclusive && node && node->prefix) stack[cnt++] = node; if (cnt <= 0) { return NULL; } while (--cnt >= 0) { node = stack[cnt]; if (comp_with_mask(prefix_tochar(node->prefix), prefix_tochar(prefix), node->prefix->bitlen) && node->prefix->bitlen <= bitlen) { return (node); } } return NULL; } patricia_node_t* patricia_search_best(patricia_tree_t* patricia, prefix_t* prefix) { return patricia_search_best2(patricia, prefix, 1); } patricia_node_t* patricia_lookup(patricia_tree_t* patricia, prefix_t* prefix) { patricia_node_t* node = nullptr; patricia_node_t* new_node = nullptr; patricia_node_t* parent = nullptr; patricia_node_t* glue = nullptr; u_char* addr = nullptr; u_char* test_addr = nullptr; u_int bitlen = 0; u_int check_bit = 0; u_int differ_bit = 0; int i = 0; int j = 0; int r = 0; assert(patricia); assert(prefix); assert(prefix->bitlen <= patricia->maxbits); if (patricia->head == NULL) { node = (patricia_node_t*)calloc(1, sizeof *node); node->bit = prefix->bitlen; node->prefix = Ref_Prefix(prefix); node->parent = NULL; node->l = node->r = NULL; node->data = NULL; patricia->head = node; // printf("patricia_lookup: new_node #0 %s/%d (head)\n", prefix_toa(prefix), prefix->bitlen); patricia->num_active_node++; return node; } addr = prefix_touchar(prefix); bitlen = prefix->bitlen; node = patricia->head; while (node->bit < bitlen || node->prefix == NULL) { if (node->bit < patricia->maxbits && BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { if (node->r == NULL) break; node = node->r; } else { if (node->l == NULL) break; node = node->l; } assert(node); } assert(node->prefix); // printf("patricia_lookup: stop at %s/%d\n", prefix_toa(node->prefix), node->prefix->bitlen); test_addr = prefix_touchar(node->prefix); /* find the first bit different */ check_bit = (node->bit < bitlen) ? node->bit : bitlen; differ_bit = 0; for (i = 0; i * 8 < check_bit; i++) { if ((r = (addr[i] ^ test_addr[i])) == 0) { differ_bit = (i + 1) * 8; continue; } /* I know the better way, but for now */ for (j = 0; j < 8; j++) { if (BIT_TEST(r, (0x80 >> j))) break; } /* must be found */ assert(j < 8); differ_bit = i * 8 + j; break; } if (differ_bit > check_bit) differ_bit = check_bit; // printf("patricia_lookup: differ_bit %d\n", differ_bit); parent = node->parent; while (parent && parent->bit >= differ_bit) { node = parent; parent = node->parent; } if (differ_bit == bitlen && node->bit == bitlen) { if (node->prefix) { // fprintf("patricia_lookup: found %s/%d\n", prefix_toa(node->prefix), node->prefix->bitlen); return (node); } node->prefix = Ref_Prefix(prefix); // fprintf("patricia_lookup: new node #1 %s/%d (glue mod)\n", prefix_toa(prefix), prefix->bitlen); assert(node->data == NULL); return (node); } new_node = (patricia_node_t*)calloc(1, sizeof *new_node); new_node->bit = prefix->bitlen; new_node->prefix = Ref_Prefix(prefix); new_node->parent = NULL; new_node->l = new_node->r = NULL; new_node->data = NULL; patricia->num_active_node++; if (node->bit == differ_bit) { new_node->parent = node; if (node->bit < patricia->maxbits && BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { assert(node->r == NULL); node->r = new_node; } else { assert(node->l == NULL); node->l = new_node; } // printf("patricia_lookup: new_node #2 %s/%d (child)\n", prefix_toa(prefix), prefix->bitlen); return new_node; } if (bitlen == differ_bit) { if (bitlen < patricia->maxbits && BIT_TEST(test_addr[bitlen >> 3], 0x80 >> (bitlen & 0x07))) { new_node->r = node; } else { new_node->l = node; } new_node->parent = node->parent; if (node->parent == NULL) { assert(patricia->head == node); patricia->head = new_node; } else if (node->parent->r == node) { node->parent->r = new_node; } else { node->parent->l = new_node; } node->parent = new_node; // printf("patricia_lookup: new_node #3 %s/%d (parent)\n", prefix_toa(prefix), prefix->bitlen); } else { glue = (patricia_node_t*)calloc(1, sizeof *glue); glue->bit = differ_bit; glue->prefix = NULL; glue->parent = node->parent; glue->data = NULL; patricia->num_active_node++; if (differ_bit < patricia->maxbits && BIT_TEST(addr[differ_bit >> 3], 0x80 >> (differ_bit & 0x07))) { glue->r = new_node; glue->l = node; } else { glue->r = node; glue->l = new_node; } new_node->parent = glue; if (node->parent == NULL) { assert(patricia->head == node); patricia->head = glue; } else if (node->parent->r == node) { node->parent->r = glue; } else { node->parent->l = glue; } node->parent = glue; // printf("patricia_lookup: new_node #4 %s/%d (glue+node)\n", prefix_toa(prefix), prefix->bitlen); } return new_node; } patricia_node_t* make_and_lookup(patricia_tree_t* tree, const char* prefix_as_string) { prefix_t* prefix = ascii2prefix(AF_INET, prefix_as_string); patricia_node_t* node = patricia_lookup(tree, prefix); Deref_Prefix(prefix); return node; } patricia_node_t* make_and_lookup_ipv6(patricia_tree_t* tree, const char* prefix_as_string) { prefix_t* prefix = ascii2prefix(AF_INET6, prefix_as_string); patricia_node_t* node = patricia_lookup(tree, prefix); Deref_Prefix(prefix); return node; } // Add custom pointer to this subnet leaf patricia_node_t* make_and_lookup_with_data(patricia_tree_t* tree, const char* prefix_as_string, void* user_data) { prefix_t* prefix = ascii2prefix(AF_INET, prefix_as_string); patricia_node_t* node = patricia_lookup(tree, prefix); node->data = user_data; Deref_Prefix(prefix); return node; } // Add custom pointer to subnet leaf for IPv6 patricia_node_t* make_and_lookup_ipv6_with_data(patricia_tree_t* tree, const char* prefix_as_string, void* user_data) { prefix_t* prefix = ascii2prefix(AF_INET6, prefix_as_string); patricia_node_t* node = patricia_lookup(tree, prefix); node->data = user_data; Deref_Prefix(prefix); return node; } pavel-odintsov-fastnetmon-394fbe0/src/libpatricia/patricia.hpp000066400000000000000000000063721520703010000246310ustar00rootroot00000000000000/* * $Id: patricia.h,v 1.6 2005/12/07 20:53:01 dplonka Exp $ * Dave Plonka * * This product includes software developed by the University of Michigan, * Merit Network, Inc., and their contributors. * * This file had been called "radix.h" in the MRT sources. * * I renamed it to "patricia.h" since it's not an implementation of a general * radix trie. Also, pulled in various requirements from "mrt.h" and added * some other things it could be used as a standalone API. */ #pragma once #include /* for u_* definitions (on FreeBSD 5) */ #include /* for EAFNOSUPPORT */ #ifdef _WIN32 #include #include #else #include /* for struct in_addr */ #include /* for AF_INET */ #endif #include #include class prefix4_t { public: u_short family = 0; /* AF_INET | AF_INET6 */ u_short bitlen = 0; /* same as mask? */ int ref_count = 0; /* reference count */ struct in_addr sin {}; }; class prefix_t { public: u_short family = 0; /* AF_INET | AF_INET6 */ u_short bitlen = 0; /* same as mask? */ int ref_count = 0; /* reference count */ union { struct in_addr sin; // IPV6 struct in6_addr sin6; } add; }; class patricia_node_t { public: u_int bit = 0; /* flag if this node used */ prefix_t* prefix = 0; /* who we are in patricia tree */ struct patricia_node_t* l = nullptr; // left children struct patricia_node_t* r = nullptr; // right children struct patricia_node_t* parent = nullptr; /* may be used */ void* data = nullptr; /* pointer to data */ }; class patricia_tree_t { public: patricia_node_t* head = nullptr; u_int maxbits = 0; /* for IP, 32 bit addresses */ int num_active_node = 0; /* for debug purpose */ }; // Create tree patricia_tree_t* New_Patricia(int maxbits); // Add elements to IPv4 tree patricia_node_t* make_and_lookup(patricia_tree_t* tree, const char* string); patricia_node_t* make_and_lookup_with_data(patricia_tree_t* tree, const char* string, void* user_data); // Add elements to IPv6 tree patricia_node_t* make_and_lookup_ipv6(patricia_tree_t* tree, const char* string); patricia_node_t* make_and_lookup_ipv6_with_data(patricia_tree_t* tree, const char* string, void* user_data); // Search in tree patricia_node_t* patricia_search_exact(patricia_tree_t* patricia, prefix_t* prefix); patricia_node_t* patricia_search_best(patricia_tree_t* patricia, prefix_t* prefix); patricia_node_t* patricia_search_best2(patricia_tree_t* patricia, prefix_t* prefix, int inclusive); patricia_node_t* patricia_lookup(patricia_tree_t* patricia, prefix_t* prefix); // Tree traversal void patricia_process(patricia_tree_t* patricia, std::function func); // Erase of all elements from tree void Clear_Patricia(patricia_tree_t* patricia, std::function func); // Destruction of tree void Destroy_Patricia(patricia_tree_t* patricia, std::function func); void Destroy_Patricia(patricia_tree_t* patricia); // Prefix to ASCII char* prefix_toa(prefix_t* prefix); // ASCII to prefix prefix_t* ascii2prefix(int family, const char* string); pavel-odintsov-fastnetmon-394fbe0/src/libsflow/000077500000000000000000000000001520703010000216525ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/libsflow/libsflow.cpp000066400000000000000000000440741520703010000242100ustar00rootroot00000000000000#include "libsflow.hpp" #include // log4cpp logging facility #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" extern log4cpp::Category& logger; std::string sflow_parser_log_prefix = "sflow_parser "; #define FMT_HEADER_ONLY #include "../fmt/compile.h" #include "../fmt/format.h" // Convert scoped enum to internal integer representation unsigned int get_flow_enum_type_as_number(const sflow_sample_type_t& value) { return static_cast::type>(value); } void build_ipv4_address_from_array(std::array ipv4_array_address, std::string& output_string) { // Use most efficient way to implement this transformation output_string = fmt::format(FMT_COMPILE("{}.{}.{}.{}"), int(ipv4_array_address[0]), int(ipv4_array_address[1]), int(ipv4_array_address[2]), int(ipv4_array_address[3])); } std::string build_ipv6_address_from_array(std::array ipv6_array_address) { std::stringstream buffer; for (int index = 0; index < 16; index++) { buffer << std::ios_base::hex << int(ipv6_array_address[index]); if (index + 1 != 16) { buffer << ":"; } } return buffer.str(); } std::tuple split_mixed_enterprise_and_format(int32_t enterprise_and_format) { // Get first 20 bits as enterprise int32_t enterprise = enterprise_and_format >> 12; // Get last 12 bits int32_t integer_format = enterprise_and_format & 0b00000000000000000000111111111111; return std::make_tuple(enterprise, integer_format); } // Convert arbitrary flow record structure with record samples to well formed // data bool get_records(std::vector& vector_tuple, const uint8_t* flow_record_zone_start, uint32_t number_of_flow_records, const uint8_t* current_packet_end, bool& padding_found) { const uint8_t* flow_record_start = flow_record_zone_start; for (uint32_t i = 0; i < number_of_flow_records; i++) { // Check that we have at least 2 4 byte integers here if (current_packet_end - flow_record_start < 8) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "do not have enough space in packet to read flow type and length"; return false; } int32_t element_type = get_int_value_by_32bit_shift(flow_record_start, 0); int32_t element_length = get_int_value_by_32bit_shift(flow_record_start, 1); // sFlow v5 standard does not constrain size of each sample but // we need to apply some reasonable limits on this value to avoid possible integer overflows in boundary checks // code below and I've decided to limit sample size by maximum UDP packet size if (element_length > max_udp_packet_size) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "Element length " << element_length << " exceeds maximum allowed size: " << max_udp_packet_size; return false; } const uint8_t* flow_record_data_ptr = flow_record_start + sizeof(element_type) + sizeof(element_length); const uint8_t* flow_record_end = flow_record_data_ptr + element_length; if (flow_record_end > current_packet_end) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "flow record payload is outside packet bounds"; return false; } vector_tuple.push_back(std::make_tuple(element_type, flow_record_data_ptr, element_length)); flow_record_start = flow_record_end; } // Well, I do not think that we need this kind of check because it should be blocked in previous section but let's keep it int64_t packet_padding = current_packet_end - flow_record_start; if (packet_padding < 0) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "negative padding is not possible"; return false; } // Return information that we found padding. Just for information purposes if (packet_padding != 0) { padding_found = true; } /* * I just discovered that Brocade devices (Brocade ICX6610) could add 4 byte padding at the end of packet. * So I see no reasons to return error here. */ return true; } // Convert arbitrary data structure with samples to vector with meta data and // pointers to real data bool get_all_samples(std::vector& vector_sample, const uint8_t* samples_block_start, const uint8_t* total_packet_end, int32_t samples_count, bool& discovered_padding) { const uint8_t* sample_start = samples_block_start; for (int i = 0; i < samples_count; i++) { if (total_packet_end - sample_start < 8) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we do not have sample format and length information here"; return false; } int32_t enterprise_with_format = get_int_value_by_32bit_shift(sample_start, 0); int32_t sample_length = get_int_value_by_32bit_shift(sample_start, 1); // sFlow v5 standard does not constrain size of each sample but // we need to apply some reasonable limits on this value to avoid possible integer overflows in boundary checks // code below and I've decided to limit sample size by maximum UDP packet size if (sample_length > max_udp_packet_size) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "Sample length " << sample_length << " exceeds maximum allowed size: " << max_udp_packet_size; return false; } // Get first 20 bits as enterprise int32_t enterprise = enterprise_with_format >> 12; // Get last 12 bits as format, zeroify first 20 bits int32_t integer_format = enterprise_with_format & 0b00000000000000000000111111111111; const uint8_t* data_block_start = sample_start + sizeof(enterprise_with_format) + sizeof(sample_length); // Skip format,length and data const uint8_t* this_sample_end = data_block_start + sample_length; // Check sample bounds inside packet if (this_sample_end > total_packet_end) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we have tried to read outside the packet"; return false; } vector_sample.push_back(std::make_tuple(enterprise, integer_format, data_block_start, sample_length)); // This sample end become next sample start sample_start = this_sample_end; } // Sanity check! We should achieve end of whole packet in any case // We discovered that Brocade MLXe-4 adds 20 bytes at the end of sFlow packet and this check prevent FastNetMon from // correct work // And I do not think that this change could harm other customers if (sample_start != total_packet_end) { // logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix // << "We haven't acheived end of whole packed due to some reasons! " // "Some samples skipped"; discovered_padding = true; } return true; } int32_t get_int_value_by_32bit_shift(const uint8_t* payload_ptr, unsigned int shift) { return fast_ntoh(*(int32_t*)(payload_ptr + shift * 4)); } bool get_all_counter_records(std::vector& counter_record_sample_vector, const uint8_t* data_block_start, const uint8_t* data_block_end, uint32_t number_of_records) { const uint8_t* record_start = data_block_start; for (uint32_t i = 0; i < number_of_records; i++) { const uint8_t* payload_ptr = record_start + sizeof(uint32_t) + sizeof(uint32_t); if (payload_ptr >= data_block_end) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we could not read flow counter record, too short packet"; return false; } int32_t enterprise_and_format = get_int_value_by_32bit_shift(record_start, 0); uint32_t record_length = get_int_value_by_32bit_shift(record_start, 1); // sFlow v5 standard does not constrain size of each sample but // we need to apply some reasonable limits on this value to avoid possible integer overflows in boundary checks // code below and I've decided to limit sample size by maximum UDP packet size if (record_length > max_udp_packet_size) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "Record length " << record_length << " exceeds maximum allowed size: " << max_udp_packet_size; return false; } const uint8_t* current_record_end = payload_ptr + record_length; if (current_record_end > data_block_end) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "record payload is outside of record border"; return false; } int32_t enterprise = 0; int32_t integer_format = 0; std::tie(enterprise, integer_format) = split_mixed_enterprise_and_format(enterprise_and_format); // std::cout << "enterprise: " << enterprise << " integer_format: " << // integer_format << // std::endl; counter_record_sample_t counter_record_sample{}; counter_record_sample.enterprise = enterprise; counter_record_sample.format = integer_format; counter_record_sample.length = record_length; counter_record_sample.pointer = payload_ptr; counter_record_sample_vector.push_back(counter_record_sample); record_start = current_record_end; } if (record_start != data_block_end) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we haven't read whole packet in counter record: " << record_start - data_block_end; return false; } return true; } sflow_sample_type_t sflow_sample_type_from_integer(int32_t format_as_integer) { if (format_as_integer < get_flow_enum_type_as_number(sflow_sample_type_t::FLOW_SAMPLE) || format_as_integer > get_flow_enum_type_as_number(sflow_sample_type_t::EXPANDED_COUNTER_SAMPLE)) { return sflow_sample_type_t::BROKEN_TYPE; } return static_cast(format_as_integer); } bool read_sflow_header(const uint8_t* payload_ptr, unsigned int payload_length, sflow_packet_header_unified_accessor& sflow_header_accessor) { // zero sized packet if (payload_ptr == NULL || payload_length == 0) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "zero sized packet could not be parsed"; return false; } // if received packet is smaller than smallest possible header size if (payload_length < sizeof(sflow_packet_header_v4_t)) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "received packet too small. It shorter than sFlow header"; return false; } int32_t sflow_version = get_int_value_by_32bit_shift(payload_ptr, 0); if (sflow_version != 5) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "We do not support sFlow version " << sflow_version; return false; } int32_t ip_protocol_version = get_int_value_by_32bit_shift(payload_ptr, 1); if (ip_protocol_version == 1) { // IPv4 sflow_packet_header_v4_t sflow_v4_header_struct; memcpy(&sflow_v4_header_struct, payload_ptr, sizeof(sflow_v4_header_struct)); // Convert all 32 bit values from network byte order to host byte order sflow_v4_header_struct.network_to_host_byte_order(); // sflow_v4_header_struct.print(); sflow_header_accessor = sflow_v4_header_struct; } else if (ip_protocol_version == 2) { // IPv6 // Check for packet length if (payload_length < sizeof(sflow_packet_header_v6_t)) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "received packet too small for IPv6 sFlow packet."; return false; } sflow_packet_header_v6_t sflow_v6_header_struct; memcpy(&sflow_v6_header_struct, payload_ptr, sizeof(sflow_v6_header_struct)); sflow_v6_header_struct.network_to_host_byte_order(); // Create unified accessor format sflow_header_accessor = sflow_v6_header_struct; } else { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "Unknown ip protocol version for sFlow: " << ip_protocol_version; return false; } return true; } std::string print_counter_record_sample_vector(const std::vector& counter_record_sample_vector) { std::stringstream buffer; int index = 0; for (const auto& counter_record_sample : counter_record_sample_vector) { buffer << "index: " << index << " enterprise: " << counter_record_sample.enterprise << " format: " << counter_record_sample.format << " length: " << counter_record_sample.length; //<< " pointer: " << (void*)counter_record_sample.pointer; index++; if (counter_record_sample_vector.size() != index) { buffer << ","; } } return buffer.str(); } std::string print_vector_sample_tuple(const std::vector& vector_sample_tuple) { std::stringstream buffer; int index = 0; for (auto sample_tuple : vector_sample_tuple) { buffer << "index: " << index << " enterprise: " << std::get<0>(sample_tuple) << " format: " << std::get<1>(sample_tuple) //<< " pointer: " << (void*)std::get<2>(sample_tuple) << " pointer: " << "XXX" << " length: " << std::get<3>(sample_tuple); index++; if (vector_sample_tuple.size() != index) { buffer << ","; } } return buffer.str(); } bool read_sflow_counter_header(const uint8_t* data_pointer, size_t data_length, bool expanded, sflow_counter_header_unified_accessor_t& sflow_counter_header_unified_accessor) { if (expanded) { // Expanded format if (data_length < sizeof(sflow_counter_expanded_header_t)) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "could not read counter_sample reader, too short packet"; return false; } sflow_counter_expanded_header_t sflow_counter_expanded_header; memcpy(&sflow_counter_expanded_header, data_pointer, sizeof(sflow_counter_expanded_header_t)); sflow_counter_expanded_header.network_to_host_byte_order(); // sflow_counter_expanded_header.print(); sflow_counter_header_unified_accessor = sflow_counter_expanded_header; } else { // Not expanded format if (data_length < sizeof(sflow_counter_header_t)) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "could not read counter_sample reader, too short packet"; return false; } sflow_counter_header_t sflow_counter_header; memcpy(&sflow_counter_header, data_pointer, sizeof(sflow_counter_header_t)); sflow_counter_header.network_to_host_byte_order(); // sflow_counter_header.print(); sflow_counter_header_unified_accessor = sflow_counter_header; } return true; } std::tuple split_32bit_integer_by_8_and_24_bits(uint32_t original_data) { uint32_t extracted_8bit_data = original_data >> 24; uint32_t extracted_24_bit_data = original_data & 0x0fffffff; return std::make_tuple(extracted_8bit_data, extracted_24_bit_data); } std::tuple split_32bit_integer_by_2_and_30_bits(uint32_t original_data) { uint32_t extracted_2bit_data = original_data >> 30; uint32_t extracted_30bit_data = original_data & 0b00111111111111111111111111111111; return std::make_tuple(extracted_2bit_data, extracted_30bit_data); } bool read_sflow_sample_header_unified(sflow_sample_header_unified_accessor_t& sflow_sample_header_unified_accessor, const uint8_t* data_pointer, size_t data_length, bool expanded) { if (expanded) { if (data_length < sizeof(sflow_sample_expanded_header_t)) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we have so short packet for FLOW_SAMPLE"; return false; } sflow_sample_expanded_header_t sflow_sample_expanded_header; memcpy(&sflow_sample_expanded_header, data_pointer, sizeof(sflow_sample_expanded_header_t)); sflow_sample_expanded_header.network_to_host_byte_order(); sflow_sample_header_unified_accessor = sflow_sample_expanded_header; } else { // So short data block length if (data_length < sizeof(sflow_sample_header_t)) { logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we have so short packet for FLOW_SAMPLE"; return false; } sflow_sample_header_t flow_sample_header; memcpy(&flow_sample_header, data_pointer, sizeof(flow_sample_header)); flow_sample_header.network_to_host_byte_order(); sflow_sample_header_unified_accessor = flow_sample_header; } return true; } std::string print_vector_tuple(const std::vector& vector_tuple) { std::stringstream buffer; int index = 0; for (record_tuple_t record_tuple : vector_tuple) { buffer << "index: " << index << " " << "type: " << std::get<0>(record_tuple) << " " << "length: " << std::get<2>(record_tuple); index++; if (vector_tuple.size() != index) { buffer << ","; } } return buffer.str(); } pavel-odintsov-fastnetmon-394fbe0/src/libsflow/libsflow.hpp000066400000000000000000001115521520703010000242110ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include "../fast_endianless.hpp" // We need it for sanity checks const uint32_t max_udp_packet_size = 65535; // We need to limit number of samples by reasonable number const int32_t max_sflow_sample_number = 256; // We need to limit number of counter samples by reasonable number const uint32_t max_number_of_counter_records = 256; // We need to limit number of flow samples by reasonable number const uint32_t max_number_of_flow_records = 256; enum class sflow_sample_type_t : unsigned int { FLOW_SAMPLE = 1, COUNTER_SAMPLE = 2, EXPANDED_FLOW_SAMPLE = 3, EXPANDED_COUNTER_SAMPLE = 4, BROKEN_TYPE = UINT_MAX, }; // This one stores protocol of header https://sflow.org/sflow_version_5.txt enum sflow_header_protocol { SFLOW_HEADER_PROTOCOL_ETHERNET = 1, // Typically, it's Ethernet SFLOW_HEADER_PROTOCOL_IPv4 = 11, SFLOW_HEADER_PROTOCOL_IPv6 = 12, }; // https://sflow.org/sflow_version_5.txt // Formats 1 & 2 apply only to an output interface and never to an input interface. A packet is always received on a single (possibly unknown) interface. enum sflow_port_format { SFLOW_PORT_FORMAT_SINGLE_INTERFACE = 0, SFLOW_PORT_FORMAT_PACKET_DISCARDED = 1, SFLOW_PORT_FORMAT_MULTIPLE_INTERFACES = 2, }; // Old fashioned not typed enums for fast comparisons and assignments to // integers enum sflow_sample_type_not_typed_t { SFLOW_SAMPLE_TYPE_FLOW_SAMPLE = 1, SFLOW_SAMPLE_TYPE_COUNTER_SAMPLE = 2, SFLOW_SAMPLE_TYPE_EXPANDED_FLOW_SAMPLE = 3, SFLOW_SAMPLE_TYPE_EXPANDED_COUNTER_SAMPLE = 4, }; enum sflow_record_types_not_typed_t { SFLOW_RECORD_TYPE_RAW_PACKET_HEADER = 1, SFLOW_RECORD_TYPE_EXTENDED_SWITCH_DATA = 1001, SFLOW_RECORD_TYPE_EXTENDED_ROUTER_DATA = 1002, SFLOW_RECORD_TYPE_EXTENDED_GATEWAY_DATA = 1003 }; enum class sample_counter_types_t : unsigned int { GENERIC_INTERFACE_COUNTERS = 1, ETHERNET_INTERFACE_COUNTERS = 2, BROKEN_COUNTER = UINT_MAX }; // These types are not sFlow protocol specific, we use them only in our own logic class counter_record_sample_t { public: uint32_t enterprise = 0; uint32_t format = 0; ssize_t length = 0; const uint8_t* pointer = nullptr; }; // Element type, pointer, length typedef std::tuple record_tuple_t; // Enterprise, integer_format, data_block_start, sample_length typedef std::tuple sample_tuple_t; // We keep these prototypes here because we use them from our class definitions std::tuple split_32bit_integer_by_2_and_30_bits(uint32_t original_data); std::tuple split_32bit_integer_by_8_and_24_bits(uint32_t original_data); void build_ipv4_address_from_array(std::array ipv4_array_address, std::string& output_string); std::string build_ipv6_address_from_array(std::array ipv6_array_address); class __attribute__((__packed__)) sflow_sample_header_as_struct_t { public: union __attribute__((__packed__)) { uint32_t enterprise : 20, sample_type : 12; uint32_t enterprise_and_sample_type_as_integer = 0; }; uint32_t sample_length = 0; void host_byte_order_to_network_byte_order() { enterprise_and_sample_type_as_integer = fast_hton(enterprise_and_sample_type_as_integer); sample_length = fast_hton(sample_length); } }; static_assert(sizeof(sflow_sample_header_as_struct_t) == 8, "Bad size for sflow_sample_header_as_struct_t"); class __attribute__((__packed__)) sflow_record_header_t { public: uint32_t record_type = 0; uint32_t record_length = 0; void host_byte_order_to_network_byte_order() { record_type = fast_hton(record_type); record_length = fast_hton(record_length); } }; static_assert(sizeof(sflow_record_header_t) == 8, "Bad size for sflow_record_header_t"); // Structure which describes sampled raw ethernet packet from switch class __attribute__((__packed__)) sflow_raw_protocol_header_t { public: uint32_t header_protocol{ 0 }; uint32_t frame_length_before_sampling{ 0 }; uint32_t number_of_bytes_removed_from_packet{ 0 }; uint32_t header_size{ 0 }; // Convert byte order from network to host byte order void network_to_host_byte_order() { header_protocol = fast_ntoh(header_protocol); frame_length_before_sampling = fast_ntoh(frame_length_before_sampling); number_of_bytes_removed_from_packet = fast_ntoh(number_of_bytes_removed_from_packet); header_size = fast_ntoh(header_size); } // Convert byte order from host to network void host_byte_order_to_network_byte_order() { header_protocol = fast_hton(header_protocol); frame_length_before_sampling = fast_hton(frame_length_before_sampling); number_of_bytes_removed_from_packet = fast_hton(number_of_bytes_removed_from_packet); header_size = fast_hton(header_size); } std::string print() { std::stringstream buffer; buffer << "header_protocol: " << header_protocol << " " << "frame_length_before_sampling: " << frame_length_before_sampling << " " << "number_of_bytes_removed_from_packet: " << number_of_bytes_removed_from_packet << " " << "header_size: " << header_size << std::endl; return buffer.str(); } }; static_assert(sizeof(sflow_raw_protocol_header_t) == 16, "Broken size for sflow_raw_protocol_header_t"); class __attribute__((__packed__)) sflow_sample_header_t { public: uint32_t sample_sequence_number = 0; // sample sequence number union __attribute__((__packed__)) { uint32_t source_id_with_id_type{ 0 }; // source id type + source id uint32_t source_id : 24, source_id_type : 8; }; uint32_t sampling_rate{ 0 }; // sampling ratio uint32_t sample_pool{ 0 }; // number of sampled packets uint32_t drops_count{ 0 }; // number of drops due to hardware overload // For both _port fields we have first two 2 bits identifying format of interface and remaining 30 bits are port number uint32_t input_port{ 0 }; // input port uint32_t output_port{ 0 }; // output port uint32_t number_of_flow_records{ 0 }; // Convert all fields to host byte order (little endian) void network_to_host_byte_order() { sample_sequence_number = fast_ntoh(sample_sequence_number); sampling_rate = fast_ntoh(sampling_rate); sample_pool = fast_ntoh(sample_pool); drops_count = fast_ntoh(drops_count); number_of_flow_records = fast_ntoh(number_of_flow_records); input_port = fast_ntoh(input_port); output_port = fast_ntoh(output_port); source_id_with_id_type = fast_ntoh(source_id_with_id_type); } // Convert all fields ti network byte order (big endian) void host_byte_order_to_network_byte_order() { sample_sequence_number = fast_hton(sample_sequence_number); sampling_rate = fast_hton(sampling_rate); sample_pool = fast_hton(sample_pool); drops_count = fast_hton(drops_count); number_of_flow_records = fast_hton(number_of_flow_records); input_port = fast_hton(input_port); output_port = fast_hton(output_port); source_id_with_id_type = fast_hton(source_id_with_id_type); } std::string print() { std::stringstream buffer; buffer << "sample_sequence_number: " << sample_sequence_number << " " << "sampling_rate: " << sampling_rate << " " << "sample_pool: " << sample_pool << " " << "drops_count: " << drops_count << " " << "number_of_flow_records: " << number_of_flow_records; return buffer.str(); } }; static_assert(sizeof(sflow_sample_header_t) == 32, "Broken size for sflow_sample_header_t"); // This header format is really close to "sflow_sample_header_t" but we do not // encode formats in // value class __attribute__((__packed__)) sflow_sample_expanded_header_t { public: uint32_t sample_sequence_number = 0; // sample sequence number uint32_t source_id_type = 0; // source id type uint32_t source_id_index = 0; // source id index uint32_t sampling_rate = 0; // sampling ratio uint32_t sample_pool = 0; // number of sampled packets uint32_t drops_count = 0; // number of drops due to hardware overload // Type 0 is single interface, 1 is discarded traffic, 2 is multiple interfaces uint32_t input_port_type = 0; // input port type uint32_t input_port_index = 0; // input port index // Type 0 is single interface, 1 is discarded traffic, 2 is multiple interfaces uint32_t output_port_type = 0; // output port type uint32_t output_port_index = 0; // outpurt port index uint32_t number_of_flow_records = 0; void network_to_host_byte_order() { sample_sequence_number = fast_ntoh(sample_sequence_number); source_id_type = fast_ntoh(source_id_type); source_id_index = fast_ntoh(source_id_index); sampling_rate = fast_ntoh(sampling_rate); sample_pool = fast_ntoh(sample_pool); drops_count = fast_ntoh(drops_count); input_port_type = fast_ntoh(input_port_type); input_port_index = fast_ntoh(input_port_index); output_port_type = fast_ntoh(output_port_type); output_port_index = fast_ntoh(output_port_index); number_of_flow_records = fast_ntoh(number_of_flow_records); } std::string print() { std::stringstream buffer; std::string delimiter = ","; buffer << "sample_sequence_number: " << sample_sequence_number << delimiter << "source_id_type: " << source_id_type << delimiter << "source_id_index: " << source_id_index << delimiter << "sampling_rate: " << sampling_rate << delimiter << "sample_pool: " << sample_pool << delimiter << "drops_count: " << drops_count << delimiter << "input_port_type: " << input_port_type << delimiter << "input_port_index: " << input_port_index << delimiter << "output_port_type: " << output_port_type << delimiter << "output_port_index: " << output_port_index << delimiter << "number_of_flow_records: " << number_of_flow_records; return buffer.str(); } }; static_assert(sizeof(sflow_sample_expanded_header_t) == 44, "Broken size for sflow_sample_expanded_header_t"); // Unified accessor for sflow_sample_header_t sflow_sample_expanded_header_t // classes. class sflow_sample_header_unified_accessor_t { public: uint32_t sample_sequence_number = 0; // sample sequence number uint32_t source_id_type = 0; // source id type uint32_t source_id_index = 0; // source id index uint32_t sampling_rate = 0; // sampling ratio uint32_t sample_pool = 0; // number of sampled packets uint32_t drops_count = 0; // number of drops due to hardware overload // Type 0 is single interface, 1 is discarded traffic, 2 is multiple interfaces uint32_t input_port_type = 0; // input port type uint32_t input_port_index = 0; // input port index // Type 0 is single interface, 1 is discarded traffic, 2 is multiple interfaces uint32_t output_port_type = 0; // output port type uint32_t output_port_index = 0; // outpurt port index uint32_t number_of_flow_records = 0; ssize_t original_payload_length = 0; uint32_t get_sample_sequence_number() { return sample_sequence_number; } uint32_t get_source_id_type() { return source_id_type; } uint32_t get_source_id_index() { return source_id_index; } uint32_t get_sampling_rate() { return sampling_rate; } uint32_t get_sample_pool() { return sample_pool; } uint32_t get_drops_count() { return drops_count; } uint32_t get_input_port_type() { return input_port_type; } uint32_t get_input_port_index() { return input_port_index; } uint32_t get_output_port_type() { return output_port_type; } uint32_t get_output_port_index() { return output_port_index; } uint32_t get_number_of_flow_records() { return number_of_flow_records; } ssize_t get_original_payload_length() { return original_payload_length; } sflow_sample_header_unified_accessor_t() { } sflow_sample_header_unified_accessor_t(sflow_sample_header_t sflow_sample_header) { sample_sequence_number = sflow_sample_header.sample_sequence_number; sampling_rate = sflow_sample_header.sampling_rate; sample_pool = sflow_sample_header.sample_pool; drops_count = sflow_sample_header.drops_count; number_of_flow_records = sflow_sample_header.number_of_flow_records; std::tie(input_port_type, input_port_index) = split_32bit_integer_by_2_and_30_bits(sflow_sample_header.input_port); std::tie(output_port_type, output_port_index) = split_32bit_integer_by_2_and_30_bits(sflow_sample_header.output_port); std::tie(source_id_type, source_id_index) = split_32bit_integer_by_8_and_24_bits(sflow_sample_header.source_id_with_id_type); original_payload_length = sizeof(sflow_sample_header_t); } sflow_sample_header_unified_accessor_t(sflow_sample_expanded_header_t sflow_sample_expanded_header) { sample_sequence_number = sflow_sample_expanded_header.sample_sequence_number; source_id_type = sflow_sample_expanded_header.source_id_type; source_id_index = sflow_sample_expanded_header.source_id_index; sampling_rate = sflow_sample_expanded_header.sampling_rate; sample_pool = sflow_sample_expanded_header.sample_pool; drops_count = sflow_sample_expanded_header.drops_count; input_port_type = sflow_sample_expanded_header.input_port_type; input_port_index = sflow_sample_expanded_header.input_port_index; output_port_type = sflow_sample_expanded_header.output_port_type; output_port_index = sflow_sample_expanded_header.output_port_index; number_of_flow_records = sflow_sample_expanded_header.number_of_flow_records; original_payload_length = sizeof(sflow_sample_expanded_header_t); } std::string print() const { std::stringstream buffer; buffer << "sample_sequence_number: " << sample_sequence_number << " " << "source_id_type: " << source_id_type << " " << "source_id_index: " << source_id_index << " " << "sampling_rate: " << sampling_rate << " " << "sample_pool: " << sample_pool << " " << "drops_count: " << drops_count << " " << "input_port_type: " << input_port_type << " " << "input_port_index: " << input_port_index << " " << "output_port_type: " << output_port_type << " " << "output_port_index: " << output_port_index << " " << "number_of_flow_records: " << number_of_flow_records; return buffer.str(); } }; // IP protocol version use by sflow agent enum sflow_agent_ip_protocol_version_not_typed : int32_t { SFLOW_AGENT_PROTOCOL_VERSION_IPv4 = 1, SFLOW_AGENT_PROTOCOL_VERSION_IPV6 = 2, }; enum sflow_address_type { SFLOW_ADDRESS_TYPE_UNDEFINED = 0, SFLOW_ADDRESS_TYPE_IPv4 = 1, SFLOW_ADDRESS_TYPE_IPV6 = 2 }; // with __attribute__((__packed__)) we have disabled any paddings inside this // struct template class __attribute__((__packed__)) sflow_packet_header { public: sflow_packet_header() { static_assert(address_length == 4 or address_length == 16, "You have specified wrong value for template"); } // 2, 4, 5 int32_t sflow_version{ 5 }; // IPv4: 1 (SFLOW_AGENT_PROTOCOL_VERSION_IPv4), IPv6: 2 // (SFLOW_AGENT_PROTOCOL_VERSION_IPV6) int32_t agent_ip_version{ 1 }; std::array address_v4_or_v6{}; uint32_t sub_agent_id{ 1 }; uint32_t datagram_sequence_number{ 0 }; // Device uptime in milliseconds uint32_t device_uptime{ 0 }; uint32_t datagram_samples_count{ 0 }; // Convert all structure fields to host byte order void network_to_host_byte_order() { sflow_version = fast_ntoh(sflow_version); agent_ip_version = fast_ntoh(agent_ip_version); sub_agent_id = fast_ntoh(sub_agent_id); datagram_sequence_number = fast_ntoh(datagram_sequence_number); device_uptime = fast_ntoh(device_uptime); datagram_samples_count = fast_ntoh(datagram_samples_count); } // Convert all structure fields to network byte order void host_byte_order_to_network_byte_order() { sflow_version = fast_hton(sflow_version); agent_ip_version = fast_hton(agent_ip_version); sub_agent_id = fast_hton(sub_agent_id); datagram_sequence_number = fast_hton(datagram_sequence_number); device_uptime = fast_hton(device_uptime); datagram_samples_count = fast_hton(datagram_samples_count); } std::string print() const { std::stringstream buffer; buffer << "sflow_version: " << sflow_version << std::endl << "agent_ip_version: " << agent_ip_version << std::endl << "sub_agent_id: " << sub_agent_id << std::endl; if (address_length == 4) { std::string string_ipv4_address; build_ipv4_address_from_array(address_v4_or_v6, string_ipv4_address); buffer << "agent_ip_address: " << string_ipv4_address << std::endl; } else { buffer << "agent_ip_address: " << build_ipv6_address_from_array(address_v4_or_v6) << std::endl; } buffer << "datagram_sequence_number: " << datagram_sequence_number << std::endl << "device_uptime: " << device_uptime << std::endl << "datagram_samples_count: " << datagram_samples_count << std::endl; return buffer.str(); } }; using sflow_packet_header_v4_t = sflow_packet_header<4>; using sflow_packet_header_v6_t = sflow_packet_header<16>; static_assert(sizeof(sflow_packet_header_v4_t) == 28, "Broken size for packed IPv4 structure"); static_assert(sizeof(sflow_packet_header_v6_t) == 40, "Broken size for packed IPv6 structure"); class sflow_packet_header_unified_accessor { private: int32_t sflow_version = 0; int32_t agent_ip_version = 0; std::string agent_ip_address = ""; int32_t sub_agent_id = 0; int32_t datagram_sequence_number = 0; int32_t device_uptime = 0; int32_t datagram_samples_count = 0; ssize_t original_payload_length = 0; public: int32_t get_sflow_version() const { return sflow_version; } int32_t get_agent_ip_version() const { return agent_ip_version; } std::string get_agent_ip_address() const { return agent_ip_address; } int32_t get_sub_agent_id() const { return sub_agent_id; } int32_t get_datagram_sequence_number() const { return datagram_sequence_number; }; int32_t get_device_uptime() const { return device_uptime; } int32_t get_datagram_samples_count() const { return datagram_samples_count; }; ssize_t get_original_payload_length() const { return original_payload_length; } std::string print() const { std::stringstream buffer; buffer << "sflow_version: " << sflow_version << " " << "agent_ip_version: " << agent_ip_version << " " << "agent_ip_address: " << agent_ip_address << " " << "sub_agent_id: " << sub_agent_id << " " << "datagram_sequence_number: " << datagram_sequence_number << " " << "device_uptime: " << device_uptime << " " << "datagram_samples_count: " << datagram_samples_count << " " << "original_payload_length: " << original_payload_length; return buffer.str(); } sflow_packet_header_unified_accessor() { } sflow_packet_header_unified_accessor(sflow_packet_header_v4_t sflow_packet_header_v4) { sflow_version = sflow_packet_header_v4.sflow_version; agent_ip_version = sflow_packet_header_v4.agent_ip_version; sub_agent_id = sflow_packet_header_v4.sub_agent_id; datagram_sequence_number = sflow_packet_header_v4.datagram_sequence_number; device_uptime = sflow_packet_header_v4.device_uptime; datagram_samples_count = sflow_packet_header_v4.datagram_samples_count; build_ipv4_address_from_array(sflow_packet_header_v4.address_v4_or_v6, agent_ip_address); original_payload_length = sizeof(sflow_packet_header_v4); } sflow_packet_header_unified_accessor(sflow_packet_header_v6_t sflow_packet_header_v6) { sflow_version = sflow_packet_header_v6.sflow_version; agent_ip_version = sflow_packet_header_v6.agent_ip_version; sub_agent_id = sflow_packet_header_v6.sub_agent_id; datagram_sequence_number = sflow_packet_header_v6.datagram_sequence_number; device_uptime = sflow_packet_header_v6.device_uptime; datagram_samples_count = sflow_packet_header_v6.datagram_samples_count; agent_ip_address = build_ipv6_address_from_array(sflow_packet_header_v6.address_v4_or_v6); original_payload_length = sizeof(sflow_packet_header_v6); } }; // This structure keeps information about gateway details, we use it to parse only few first fields class __attribute__((__packed__)) sflow_extended_gateway_information_t { public: // Must be IPv4 only, for IPv6 we need another structure uint32_t next_hop_address_type = 0; uint32_t next_hop = 0; uint32_t router_asn = 0; uint32_t source_asn = 0; uint32_t peer_asn = 0; }; class __attribute__((__packed__)) sflow_counter_header_t { public: uint32_t sample_sequence_number = 0; uint32_t source_type_with_id = 0; uint32_t number_of_counter_records = 0; void network_to_host_byte_order() { sample_sequence_number = fast_ntoh(sample_sequence_number); source_type_with_id = fast_ntoh(source_type_with_id); number_of_counter_records = fast_ntoh(number_of_counter_records); } std::string print() const { std::stringstream buffer; buffer << "sample_sequence_number: " << sample_sequence_number << " " << "source_type_with_id: " << source_type_with_id << " " << "number_of_counter_records: " << number_of_counter_records << std::endl; return buffer.str(); } }; static_assert(sizeof(sflow_counter_header_t) == 12, "Broken size for sflow_counter_header_t"); // Expanded form of sflow_counter_header_t class __attribute__((__packed__)) sflow_counter_expanded_header_t { public: uint32_t sample_sequence_number = 0; uint32_t source_id_type = 0; uint32_t source_id_index = 0; uint32_t number_of_counter_records = 0; void network_to_host_byte_order() { sample_sequence_number = fast_ntoh(sample_sequence_number); source_id_type = fast_ntoh(source_id_type); source_id_index = fast_ntoh(source_id_index); number_of_counter_records = fast_ntoh(number_of_counter_records); } std::string print() const { std::stringstream buffer; buffer << "sample_sequence_number: " << sample_sequence_number << " " << "source_id_type: " << source_id_type << " " << "source_id_index: " << source_id_index << " " << "number_of_counter_records: " << number_of_counter_records << std::endl; return buffer.str(); } }; static_assert(sizeof(sflow_counter_expanded_header_t) == 16, "Broken size for sflow_counter_expanded_header_t"); // Unified accessor for sflow_counter_header_t and // sflow_counter_expanded_header_t class sflow_counter_header_unified_accessor_t { private: uint32_t sample_sequence_number = 0; uint32_t source_id_type = 0; uint32_t source_id_index = 0; uint32_t number_of_counter_records = 0; ssize_t original_payload_length = 0; bool expanded = false; public: uint32_t get_sample_sequence_number() { return sample_sequence_number; } uint32_t get_source_id_type() { return source_id_type; } uint32_t get_source_id_index() { return source_id_index; } uint32_t get_number_of_counter_records() { return number_of_counter_records; } ssize_t get_original_payload_length() { return original_payload_length; } bool get_expaned() { return expanded; } sflow_counter_header_unified_accessor_t() { // default constructor } sflow_counter_header_unified_accessor_t(sflow_counter_header_t sflow_counter_header) { sample_sequence_number = sflow_counter_header.sample_sequence_number; // Get first two bytes std::tie(source_id_type, source_id_index) = split_32bit_integer_by_2_and_30_bits(sflow_counter_header.source_type_with_id); number_of_counter_records = sflow_counter_header.number_of_counter_records; original_payload_length = sizeof(sflow_counter_header_t); expanded = false; } sflow_counter_header_unified_accessor_t(sflow_counter_expanded_header_t sflow_counter_expanded_header) { sample_sequence_number = sflow_counter_expanded_header.sample_sequence_number; source_id_type = sflow_counter_expanded_header.source_id_type; source_id_index = sflow_counter_expanded_header.source_id_index; number_of_counter_records = sflow_counter_expanded_header.number_of_counter_records; original_payload_length = sizeof(sflow_counter_expanded_header_t); expanded = true; } std::string print() const { std::stringstream buffer; buffer << "sample_sequence_number: " << sample_sequence_number << " " << "source_id_type: " << source_id_type << " " << "source_id_index: " << source_id_index << " " << "number_of_counter_records: " << number_of_counter_records << " " << "original_payload_length: " << original_payload_length << " " << "expanded: " << expanded; return buffer.str(); } }; class __attribute__((__packed__)) ethernet_sflow_interface_counters_t { private: uint32_t alignment_errors = 0; uint32_t fcs_errors = 0; uint32_t single_collision_frames = 0; uint32_t multiple_collision_frames = 0; uint32_t sqe_test_errors = 0; uint32_t deferred_transmissions = 0; uint32_t late_collisions = 0; uint32_t excessive_collisions = 0; uint32_t internal_mac_transmit_errors = 0; uint32_t carrier_sense_errors = 0; uint32_t frame_too_longs = 0; uint32_t internal_mac_receive_errors = 0; uint32_t symbol_errors = 0; public: uint32_t get_alignment_errors() const { return fast_ntoh(alignment_errors); } uint32_t get_fcs_errors() const { return fast_ntoh(fcs_errors); } uint32_t get_single_collision_frames() const { return fast_ntoh(single_collision_frames); } uint32_t get_multiple_collision_frames() const { return fast_ntoh(multiple_collision_frames); } uint32_t get_sqe_test_errors() const { return fast_ntoh(sqe_test_errors); } uint32_t get_deferred_transmissions() const { return fast_ntoh(deferred_transmissions); } uint32_t get_late_collisions() const { return fast_ntoh(late_collisions); } uint32_t get_excessive_collisions() const { return fast_ntoh(excessive_collisions); } uint32_t get_internal_mac_transmit_errors() const { return fast_ntoh(internal_mac_transmit_errors); } uint32_t get_carrier_sense_errors() const { return fast_ntoh(carrier_sense_errors); } uint32_t get_frame_too_longs() const { return fast_ntoh(frame_too_longs); } uint32_t get_internal_mac_receive_errors() const { return fast_ntoh(internal_mac_receive_errors); } uint32_t get_symbol_errors() const { return fast_ntoh(symbol_errors); } std::string print() const { std::stringstream buffer; std::string delimiter = ","; buffer << "alignment_errors: " << get_alignment_errors() << delimiter << "fcs_errors: " << get_fcs_errors() << delimiter << "single_collision_frames: " << get_single_collision_frames() << delimiter << "multiple_collision_frames: " << get_multiple_collision_frames() << delimiter << "sqe_test_errors: " << get_sqe_test_errors() << delimiter << "deferred_transmissions: " << get_deferred_transmissions() << delimiter << "late_collisions: " << get_late_collisions() << delimiter << "excessive_collisions: " << get_excessive_collisions() << delimiter << "internal_mac_transmit_errors: " << get_internal_mac_transmit_errors() << delimiter << "carrier_sense_errors: " << get_carrier_sense_errors() << delimiter << "frame_too_longs: " << get_frame_too_longs() << delimiter << "internal_mac_receive_errors: " << get_internal_mac_receive_errors() << delimiter << "symbol_errors: " << get_symbol_errors(); return buffer.str(); } }; static_assert(sizeof(ethernet_sflow_interface_counters_t) == 52, "Broken size for ethernet_sflow_interface_counters_t"); // http://www.sflow.org/SFLOW-STRUCTS5.txt class __attribute__((__packed__)) generic_sflow_interface_counters_t { private: uint32_t if_index = 0; uint32_t if_type = 0; uint64_t if_speed = 0; uint32_t if_direction = 0; /* derived from MAU MIB (RFC 2668) 0 = unkown, 1=full-duplex, 2=half-duplex, 3 = in, 4=out */ uint32_t if_status = 0; /* bit field with the following bits assigned bit 0 = ifAdminStatus (0 = down, 1 = up) bit 1 = ifOperStatus (0 = down, 1 = up) */ uint64_t if_in_octets = 0; uint32_t if_in_ucast_pkts = 0; uint32_t if_in_multicast_pkts = 0; uint32_t if_in_broadcast_pkts = 0; uint32_t if_in_discards = 0; uint32_t if_in_errors = 0; uint32_t if_in_unknown_protos = 0; uint64_t if_out_octets = 0; uint32_t if_out_ucast_pkts = 0; uint32_t if_out_multicast_pkts = 0; uint32_t if_out_broadcast_pkts = 0; uint32_t if_out_discards = 0; uint32_t if_out_errors = 0; uint32_t if_promiscuous_mode = 0; public: uint32_t get_if_index() const { return fast_ntoh(if_index); } uint32_t get_if_type() const { return fast_ntoh(if_type); } uint64_t get_if_speed() const { return fast_ntoh(if_speed); } uint32_t get_if_direction() const { return fast_ntoh(if_direction); } uint32_t get_if_status() const { return fast_ntoh(if_status); } uint64_t get_if_in_octets() const { return fast_ntoh(if_in_octets); } uint32_t get_if_in_ucast_pkts() const { return fast_ntoh(if_in_ucast_pkts); } uint32_t get_if_in_multicast_pkts() const { return fast_ntoh(if_in_multicast_pkts); } uint32_t get_if_in_broadcast_pkts() const { return fast_ntoh(if_in_broadcast_pkts); } uint32_t get_if_in_discards() const { return fast_ntoh(if_in_discards); } uint32_t get_if_in_errors() const { return fast_ntoh(if_in_errors); } uint32_t get_if_in_unknown_protos() const { return fast_ntoh(if_in_unknown_protos); } uint64_t get_if_out_octets() const { return fast_ntoh(if_out_octets); } uint32_t get_if_out_ucast_pkts() const { return fast_ntoh(if_out_ucast_pkts); } uint32_t get_if_out_multicast_pkts() const { return fast_ntoh(if_out_multicast_pkts); } uint32_t get_if_out_broadcast_pkts() const { return fast_ntoh(if_out_broadcast_pkts); } uint32_t get_if_out_discards() const { return fast_ntoh(if_out_discards); } uint32_t get_if_out_errors() const { return fast_ntoh(if_out_errors); } uint32_t get_if_promiscuous_mode() const { return fast_ntoh(if_promiscuous_mode); } std::string print() const { std::stringstream buffer; std::string delimiter = ","; buffer << "if_index: " << get_if_index() << delimiter << "if_type: " << get_if_type() << delimiter << "if_speed: " << get_if_speed() << delimiter << "if_direction: " << get_if_direction() << delimiter << "if_status: " << get_if_status() << delimiter << "if_in_octets: " << get_if_in_octets() << delimiter << "if_in_ucast_pkts: " << get_if_in_ucast_pkts() << delimiter << "if_in_multicast_pkts: " << get_if_in_multicast_pkts() << delimiter << "if_in_broadcast_pkts: " << get_if_in_broadcast_pkts() << delimiter << "if_in_discards: " << get_if_in_discards() << delimiter << "if_in_errors: " << get_if_in_errors() << delimiter << "if_in_unknown_protos: " << get_if_in_unknown_protos() << delimiter << "if_out_octets: " << get_if_out_octets() << delimiter << "if_out_ucast_pkts: " << get_if_out_ucast_pkts() << delimiter << "if_out_multicast_pkts: " << get_if_out_multicast_pkts() << delimiter << "if_out_broadcast_pkts: " << get_if_out_broadcast_pkts() << delimiter << "if_out_discards: " << get_if_out_discards() << delimiter << "if_out_errors: " << get_if_out_errors() << delimiter << "if_promiscuous_mode: " << get_if_promiscuous_mode(); return buffer.str(); } }; static_assert(sizeof(generic_sflow_interface_counters_t) == 88, "Broken size for generic_sflow_interface_counters_t"); // High level processing functions. They uses classes defined upper bool read_sflow_header(const uint8_t* payload_ptr, unsigned int payload_length, sflow_packet_header_unified_accessor& sflow_header_accessor); bool read_sflow_counter_header(const uint8_t* data_pointer, size_t data_length, bool expanded, sflow_counter_header_unified_accessor_t& sflow_counter_header_unified_accessor); bool read_sflow_sample_header_unified(sflow_sample_header_unified_accessor_t& sflow_sample_header_unified_accessor, const uint8_t* data_pointer, size_t data_length, bool expanded); std::string print_counter_record_sample_vector(const std::vector& counter_record_sample_vector); std::string print_vector_tuple(const std::vector& vector_tuple); std::string print_vector_sample_tuple(const std::vector& vector_sample_tuple); // Create scoped enum from integer with sanity check sflow_sample_type_t sflow_sample_type_from_integer(int32_t format_as_integer); std::tuple split_mixed_enterprise_and_format(int32_t enterprise_and_format); unsigned int get_flow_enum_type_as_number(const sflow_sample_type_t& value); bool get_all_samples(std::vector& vector_sample, const uint8_t* samples_block_start, const uint8_t* total_packet_end, int32_t samples_count, bool& discovered_padding); bool get_records(std::vector& vector_tuple, const uint8_t* flow_record_zone_start, uint32_t number_of_flow_records, const uint8_t* current_packet_end, bool& padding_found); bool get_all_counter_records(std::vector& counter_record_sample_vector, const uint8_t* data_block_start, const uint8_t* data_block_end, uint32_t number_of_records); int32_t get_int_value_by_32bit_shift(const uint8_t* payload_ptr, unsigned int shift); pavel-odintsov-fastnetmon-394fbe0/src/man/000077500000000000000000000000001520703010000206045ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/man/fastnetmon.8000066400000000000000000000015551520703010000230610ustar00rootroot00000000000000.\" Manpage for fastnetmon. .\" Contact pavel.odintsov@gmail.com to correct errors or typos. .TH man 8 "02 May 2022" "1.2.1" "fastnetmon man page" .SH NAME FastNetMon \- a high performance DoS/DDoS load analyzer built on top of multiple packet capture engines .SH SYNOPSIS fastnetmon [--daemonize] .SH DESCRIPTION FastNetMon - a high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFlow, port mirror). For more information about configuration, please look at the comments in /etc/fastnetmon.conf and check the project GitHub page: https://github.com/pavel-odintsov/fastnetmon. .SH OPTIONS fastnetmon has only a single command line option --daemonize which is used for forking and detaching it from the terminal. .SH SEE ALSO fastnetmon_client(1) .SH BUGS No known bugs. .SH AUTHOR Pavel Odintsov (pavel.odintsov@gmail.com) pavel-odintsov-fastnetmon-394fbe0/src/man/fastnetmon_client.1000066400000000000000000000007661520703010000244130ustar00rootroot00000000000000.\" Manpage for fastnetmon_client. .\" Contact pavel.odintsov@gmail.com to correct errors or typos. .TH man 1 "02 May 2022" "1.2.1" "fastnetmon_client man page" .SH NAME fastnetmon_client \- show information about top talkers and detected DDoS attacks .SH SYNOPSIS fastnetmon_client .SH DESCRIPTION Client interface for monitoring fastnetmon(1) DDoS detection toolkit. .SH OPTIONS No options here .SH SEE ALSO fastnetmon(1) .SH BUGS No known bugs. .SH AUTHOR Pavel Odintsov (pavel.odintsov@gmail.com) pavel-odintsov-fastnetmon-394fbe0/src/metrics/000077500000000000000000000000001520703010000214775ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/metrics/clickhouse.cpp000066400000000000000000001266061520703010000243470ustar00rootroot00000000000000#include "clickhouse.hpp" #include #include "../abstract_subnet_counters.hpp" #include "../fast_library.hpp" #include "../fastnetmon_types.hpp" #include "../all_logcpp_libraries.hpp" extern fastnetmon_configuration_t fastnetmon_global_configuration; extern uint64_t clickhouse_metrics_writes_total; extern uint64_t clickhouse_metrics_writes_failed; extern log4cpp::Category& logger; // I do this declaration here to avoid circular dependencies between fastnetmon_logic and this file bool get_statistics(std::vector& system_counters); class PerProtocolMetrics { public: PerProtocolMetrics() { // Per protocol packet counters tcp_packets_incoming = std::make_shared(); tcp_packets_outgoing = std::make_shared(); udp_packets_incoming = std::make_shared(); udp_packets_outgoing = std::make_shared(); icmp_packets_incoming = std::make_shared(); icmp_packets_outgoing = std::make_shared(); fragmented_packets_incoming = std::make_shared(); fragmented_packets_outgoing = std::make_shared(); tcp_syn_packets_incoming = std::make_shared(); tcp_syn_packets_outgoing = std::make_shared(); // Per protocol bytes countres tcp_bits_incoming = std::make_shared(); tcp_bits_outgoing = std::make_shared(); udp_bits_incoming = std::make_shared(); udp_bits_outgoing = std::make_shared(); icmp_bits_incoming = std::make_shared(); icmp_bits_outgoing = std::make_shared(); fragmented_bits_incoming = std::make_shared(); fragmented_bits_outgoing = std::make_shared(); tcp_syn_bits_incoming = std::make_shared(); tcp_syn_bits_outgoing = std::make_shared(); } // Per protocol packet counters std::shared_ptr tcp_packets_incoming{ nullptr }; std::shared_ptr tcp_packets_outgoing{ nullptr }; std::shared_ptr udp_packets_incoming{ nullptr }; std::shared_ptr udp_packets_outgoing{ nullptr }; std::shared_ptr icmp_packets_incoming{ nullptr }; std::shared_ptr icmp_packets_outgoing{ nullptr }; std::shared_ptr fragmented_packets_incoming{ nullptr }; std::shared_ptr fragmented_packets_outgoing{ nullptr }; std::shared_ptr tcp_syn_packets_incoming{ nullptr }; std::shared_ptr tcp_syn_packets_outgoing{ nullptr }; // Per protocol bytes counters std::shared_ptr tcp_bits_incoming{ nullptr }; std::shared_ptr tcp_bits_outgoing{ nullptr }; std::shared_ptr udp_bits_incoming{ nullptr }; std::shared_ptr udp_bits_outgoing{ nullptr }; std::shared_ptr icmp_bits_incoming{ nullptr }; std::shared_ptr icmp_bits_outgoing{ nullptr }; std::shared_ptr fragmented_bits_incoming{ nullptr }; std::shared_ptr fragmented_bits_outgoing{ nullptr }; std::shared_ptr tcp_syn_bits_incoming{ nullptr }; std::shared_ptr tcp_syn_bits_outgoing{ nullptr }; }; // Keeps pointers to Clickhouse metrics class ClickhouseHostMetrics { public: ClickhouseHostMetrics() { date_time = std::make_shared(); host = std::make_shared(); packets_incoming = std::make_shared(); packets_outgoing = std::make_shared(); bits_incoming = std::make_shared(); bits_outgoing = std::make_shared(); flows_incoming = std::make_shared(); flows_outgoing = std::make_shared(); } std::shared_ptr date_time{ nullptr }; std::shared_ptr host{ nullptr }; std::shared_ptr packets_incoming{ nullptr }; std::shared_ptr packets_outgoing{ nullptr }; std::shared_ptr bits_incoming{ nullptr }; std::shared_ptr bits_outgoing{ nullptr }; std::shared_ptr flows_incoming{ nullptr }; std::shared_ptr flows_outgoing{ nullptr }; // Per protocol metrics PerProtocolMetrics pp{}; }; // Keeps pointers to Clickhouse metrics // Slightly different from hosts: uses field network instead of host and does not have flows class ClickhouseNetworkMetrics { public: ClickhouseNetworkMetrics() { date_time = std::make_shared(); network = std::make_shared(); packets_incoming = std::make_shared(); packets_outgoing = std::make_shared(); bits_incoming = std::make_shared(); bits_outgoing = std::make_shared(); } std::shared_ptr date_time{ nullptr }; std::shared_ptr network{ nullptr }; std::shared_ptr packets_incoming{ nullptr }; std::shared_ptr packets_outgoing{ nullptr }; std::shared_ptr bits_incoming{ nullptr }; std::shared_ptr bits_outgoing{ nullptr }; // Per protocol metrics PerProtocolMetrics pp{}; }; // We use template to use it for both per host and network counters which use slightly different structures void register_clickhouse_per_protocol_metrics_block(clickhouse::Block& block, PerProtocolMetrics& metrics) { // Per packet counters block.AppendColumn("tcp_packets_incoming", metrics.tcp_packets_incoming); block.AppendColumn("tcp_packets_outgoing", metrics.tcp_packets_outgoing); block.AppendColumn("udp_packets_incoming", metrics.udp_packets_incoming); block.AppendColumn("udp_packets_outgoing", metrics.udp_packets_outgoing); block.AppendColumn("icmp_packets_incoming", metrics.icmp_packets_incoming); block.AppendColumn("icmp_packets_outgoing", metrics.icmp_packets_outgoing); block.AppendColumn("fragmented_packets_incoming", metrics.fragmented_packets_incoming); block.AppendColumn("fragmented_packets_outgoing", metrics.fragmented_packets_outgoing); block.AppendColumn("tcp_syn_packets_incoming", metrics.tcp_syn_packets_incoming); block.AppendColumn("tcp_syn_packets_outgoing", metrics.tcp_syn_packets_outgoing); // Per bit counters block.AppendColumn("tcp_bits_incoming", metrics.tcp_bits_incoming); block.AppendColumn("tcp_bits_outgoing", metrics.tcp_bits_outgoing); block.AppendColumn("udp_bits_incoming", metrics.udp_bits_incoming); block.AppendColumn("udp_bits_outgoing", metrics.udp_bits_outgoing); block.AppendColumn("icmp_bits_incoming", metrics.icmp_bits_incoming); block.AppendColumn("icmp_bits_outgoing", metrics.icmp_bits_outgoing); block.AppendColumn("fragmented_bits_incoming", metrics.fragmented_bits_incoming); block.AppendColumn("fragmented_bits_outgoing", metrics.fragmented_bits_outgoing); block.AppendColumn("tcp_syn_bits_incoming", metrics.tcp_syn_bits_incoming); block.AppendColumn("tcp_syn_bits_outgoing", metrics.tcp_syn_bits_outgoing); } void increment_clickhouse_per_protocol_counters(PerProtocolMetrics& metrics, const subnet_counter_t& current_speed_element) { metrics.tcp_packets_incoming->Append(current_speed_element.tcp.in_packets); metrics.udp_packets_incoming->Append(current_speed_element.udp.in_packets); metrics.icmp_packets_incoming->Append(current_speed_element.icmp.in_packets); metrics.fragmented_packets_incoming->Append(current_speed_element.fragmented.in_packets); metrics.tcp_syn_packets_incoming->Append(current_speed_element.tcp_syn.in_packets); metrics.tcp_bits_incoming->Append(current_speed_element.tcp.in_bytes * 8); metrics.udp_bits_incoming->Append(current_speed_element.udp.in_bytes * 8); metrics.icmp_bits_incoming->Append(current_speed_element.icmp.in_bytes * 8); metrics.fragmented_bits_incoming->Append(current_speed_element.fragmented.in_bytes * 8); metrics.tcp_syn_bits_incoming->Append(current_speed_element.tcp_syn.in_bytes * 8); metrics.tcp_packets_outgoing->Append(current_speed_element.tcp.out_packets); metrics.udp_packets_outgoing->Append(current_speed_element.udp.out_packets); metrics.icmp_packets_outgoing->Append(current_speed_element.icmp.out_packets); metrics.fragmented_packets_outgoing->Append(current_speed_element.fragmented.out_packets); metrics.tcp_syn_packets_outgoing->Append(current_speed_element.tcp_syn.out_packets); metrics.tcp_bits_outgoing->Append(current_speed_element.tcp.out_bytes * 8); metrics.udp_bits_outgoing->Append(current_speed_element.udp.out_bytes * 8); metrics.icmp_bits_outgoing->Append(current_speed_element.icmp.out_bytes * 8); metrics.fragmented_bits_outgoing->Append(current_speed_element.fragmented.out_bytes * 8); metrics.tcp_syn_bits_outgoing->Append(current_speed_element.tcp_syn.out_bytes * 8); } // Populates Clickhouse host counters using speed_element void increment_clickhouse_host_counters(ClickhouseHostMetrics& metrics, const subnet_counter_t& current_speed_element) { metrics.packets_incoming->Append(current_speed_element.total.in_packets); metrics.bits_incoming->Append(current_speed_element.total.in_bytes * 8); metrics.flows_incoming->Append(current_speed_element.in_flows); metrics.packets_outgoing->Append(current_speed_element.total.out_packets); metrics.bits_outgoing->Append(current_speed_element.total.out_bytes * 8); metrics.flows_outgoing->Append(current_speed_element.out_flows); increment_clickhouse_per_protocol_counters(metrics.pp, current_speed_element); } std::string generate_total_metrics_schema(const std::string& table_name) { std::string total_metrics_schema = "CREATE TABLE IF NOT EXISTS " + fastnetmon_global_configuration.clickhouse_metrics_database + "." + table_name + "(metricDate Date DEFAULT toDate(metricDateTime)," "metricDateTime DateTime," "direction String," "flows UInt64," "packets UInt64," "bits UInt64," "tcp_packets UInt64," "udp_packets UInt64," "icmp_packets UInt64," "fragmented_packets UInt64," "tcp_syn_packets UInt64," "dropped_packets UInt64," "tcp_bits UInt64," "udp_bits UInt64," "icmp_bits UInt64," "fragmented_bits UInt64," "tcp_syn_bits UInt64," "dropped_bits UInt64," "schema_version UInt8 Default 0 COMMENT '1'" ") ENGINE = MergeTree ORDER BY (direction, metricDate) PARTITION BY metricDate " "TTL metricDate + toIntervalDay(7) SETTINGS index_granularity=8192;"; return total_metrics_schema; } std::string generate_host_metrics_schema(std::string database_name, std::string table_name) { std::string host_metrics_schema = "CREATE TABLE IF NOT EXISTS " + database_name + "." + table_name + "(metricDate Date DEFAULT toDate(metricDateTime), " "metricDateTime DateTime, " "host String, " "packets_incoming UInt64, " "packets_outgoing UInt64, " "bits_incoming UInt64, " "bits_outgoing UInt64, " "flows_incoming UInt64, " "flows_outgoing UInt64, " "tcp_packets_incoming UInt64, tcp_packets_outgoing UInt64," "udp_packets_incoming UInt64, udp_packets_outgoing UInt64," "icmp_packets_incoming UInt64, icmp_packets_outgoing UInt64," "fragmented_packets_incoming UInt64, fragmented_packets_outgoing UInt64," "tcp_syn_packets_incoming UInt64, tcp_syn_packets_outgoing UInt64," "tcp_bits_incoming UInt64, tcp_bits_outgoing UInt64," "udp_bits_incoming UInt64, udp_bits_outgoing UInt64," "icmp_bits_incoming UInt64, icmp_bits_outgoing UInt64," "fragmented_bits_incoming UInt64, fragmented_bits_outgoing UInt64," "tcp_syn_bits_incoming UInt64, tcp_syn_bits_outgoing UInt64," "schema_version UInt8 Default 0 COMMENT '2') ENGINE = MergeTree ORDER BY (host, metricDate) PARTITION BY " "metricDate TTL metricDate + toIntervalDay(7) SETTINGS index_granularity=8192;"; return host_metrics_schema; } // Create database in Clickhouse bool create_clickhouse_database_for_metrics(fastnetmon_configuration_t& fastnetmon_global_configuration, clickhouse::Client* clickhouse_metrics_client) { // Create database for FastNetMon metrics bool we_already_have_fastnetmon_database = false; // List all databases in Clickhouse clickhouse_metrics_client->Select("SHOW DATABASES", [&](const clickhouse::Block& block) { for (size_t i = 0; i < block.GetRowCount(); ++i) { if (block[0]->As()->At(i) == fastnetmon_global_configuration.clickhouse_metrics_database) { //-V767 we_already_have_fastnetmon_database = true; } } }); if (we_already_have_fastnetmon_database) { logger << log4cpp::Priority::DEBUG << "We found database for metrics in Clickhouse"; return true; } logger << log4cpp::Priority::INFO << "We do not have database for metrics in Clickhouse. Need to create it"; try { logger << log4cpp::Priority::INFO << "Create database " + fastnetmon_global_configuration.clickhouse_metrics_database; clickhouse_metrics_client->Execute("CREATE DATABASE IF NOT EXISTS " + fastnetmon_global_configuration.clickhouse_metrics_database); } catch (const std::exception& e) { logger << log4cpp::Priority::ERROR << "Could not create database: " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not create database"; return false; } return true; } // Creates Clickhouse table using provided table name and schema bool create_clickhouse_table_using_schema(const std::string& schema, const std::string& table_name, clickhouse::Client* clickhouse_metrics_client) { try { logger << log4cpp::Priority::DEBUG << "Attempt to create table " << table_name << " if it does not exist"; clickhouse_metrics_client->Execute(schema); } catch (const std::exception& e) { logger << log4cpp::Priority::ERROR << "Could not create table " << table_name << ": " << e.what(); return false; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not create table " << table_name; return false; } return true; } // Creates tables in Clickhouse bool create_clickhouse_tables_for_metrics(fastnetmon_configuration_t& fastnetmon_global_configuration, clickhouse::Client* clickhouse_metrics_client) { auto create_databases_result = create_clickhouse_database_for_metrics(fastnetmon_global_configuration, clickhouse_metrics_client); if (!create_databases_result) { return false; } // clang-format off // Create tables for Clickhouse metrics std::string network_metrics_schema = "CREATE TABLE IF NOT EXISTS " + fastnetmon_global_configuration.clickhouse_metrics_database + ".network_metrics(" "metricDate Date DEFAULT toDate(metricDateTime)," "metricDateTime DateTime," "network String," "packets_incoming UInt64, packets_outgoing UInt64," "bits_incoming UInt64, bits_outgoing UInt64," "tcp_packets_incoming UInt64, tcp_packets_outgoing UInt64," "udp_packets_incoming UInt64, udp_packets_outgoing UInt64," "icmp_packets_incoming UInt64, icmp_packets_outgoing UInt64," "fragmented_packets_incoming UInt64, fragmented_packets_outgoing UInt64," "tcp_syn_packets_incoming UInt64, tcp_syn_packets_outgoing UInt64," "tcp_bits_incoming UInt64, tcp_bits_outgoing UInt64," "udp_bits_incoming UInt64, udp_bits_outgoing UInt64," "icmp_bits_incoming UInt64, icmp_bits_outgoing UInt64," "fragmented_bits_incoming UInt64, fragmented_bits_outgoing UInt64," "tcp_syn_bits_incoming UInt64, tcp_syn_bits_outgoing UInt64," "schema_version UInt8 Default 0 COMMENT '1'" ") ENGINE = MergeTree ORDER BY (network, metricDate) PARTITION BY metricDate TTL metricDate + toIntervalDay(7) SETTINGS index_granularity=8192;"; std::string network_metrics_ipv6_schema = "CREATE TABLE IF NOT EXISTS " + fastnetmon_global_configuration.clickhouse_metrics_database + ".network_metrics_ipv6(" "metricDate Date DEFAULT toDate(metricDateTime)," "metricDateTime DateTime," "network String," "packets_incoming UInt64, packets_outgoing UInt64," "bits_incoming UInt64, bits_outgoing UInt64," "tcp_packets_incoming UInt64, tcp_packets_outgoing UInt64," "udp_packets_incoming UInt64, udp_packets_outgoing UInt64," "icmp_packets_incoming UInt64, icmp_packets_outgoing UInt64," "fragmented_packets_incoming UInt64, fragmented_packets_outgoing UInt64," "tcp_syn_packets_incoming UInt64, tcp_syn_packets_outgoing UInt64," "tcp_bits_incoming UInt64, tcp_bits_outgoing UInt64," "udp_bits_incoming UInt64, udp_bits_outgoing UInt64," "icmp_bits_incoming UInt64, icmp_bits_outgoing UInt64," "fragmented_bits_incoming UInt64, fragmented_bits_outgoing UInt64," "tcp_syn_bits_incoming UInt64, tcp_syn_bits_outgoing UInt64," "schema_version UInt8 Default 0 COMMENT '1'" ") ENGINE = MergeTree ORDER BY (network, metricDate) PARTITION BY metricDate TTL metricDate + toIntervalDay(7) SETTINGS index_granularity=8192;"; std::string total_metrics_schema = generate_total_metrics_schema("total_metrics"); std::string total_metrics_ipv4_schema = generate_total_metrics_schema("total_metrics_ipv4"); std::string total_metrics_ipv6_schema = generate_total_metrics_schema("total_metrics_ipv6"); // clang-format on if (!create_clickhouse_table_using_schema(network_metrics_schema, "network_metrics", clickhouse_metrics_client)) { return false; } if (!create_clickhouse_table_using_schema(network_metrics_ipv6_schema, "network_metrics_ipv6", clickhouse_metrics_client)) { return false; } std::string host_metrics_schema = generate_host_metrics_schema(fastnetmon_global_configuration.clickhouse_metrics_database, "host_metrics"); if (!create_clickhouse_table_using_schema(host_metrics_schema, "host_metrics", clickhouse_metrics_client)) { return false; } std::string host_metrics_ipv6_schema = generate_host_metrics_schema(fastnetmon_global_configuration.clickhouse_metrics_database, "host_metrics_ipv6"); if (!create_clickhouse_table_using_schema(host_metrics_ipv6_schema, "host_metrics_ipv6", clickhouse_metrics_client)) { return false; } if (!create_clickhouse_table_using_schema(total_metrics_schema, "total_metrics", clickhouse_metrics_client)) { return false; } if (!create_clickhouse_table_using_schema(total_metrics_ipv4_schema, "total_metrics_ipv4", clickhouse_metrics_client)) { return false; } if (!create_clickhouse_table_using_schema(total_metrics_ipv6_schema, "total_metrics_ipv6", clickhouse_metrics_client)) { return false; } // Create table for system counters std::string system_metrics_schema = "CREATE TABLE IF NOT EXISTS " + fastnetmon_global_configuration.clickhouse_metrics_database + ".system_metrics" + "(metricDate Date DEFAULT toDate(metricDateTime), " "metricDateTime DateTime, " "name String, " "type String, " "value UInt64, " "schema_version UInt8 Default 0 COMMENT '1') ENGINE = MergeTree ORDER BY (name, metricDate) PARTITION BY " "metricDate TTL metricDate + toIntervalDay(7) SETTINGS index_granularity=8192;"; if (!create_clickhouse_table_using_schema(system_metrics_schema, "system_metrics", clickhouse_metrics_client)) { return false; } return true; } // Registers metrics to block to push them into database void register_clickhouse_host_metrics_block(clickhouse::Block& block, ClickhouseHostMetrics& metrics) { block.AppendColumn("metricDateTime", metrics.date_time); block.AppendColumn("host", metrics.host); block.AppendColumn("packets_incoming", metrics.packets_incoming); block.AppendColumn("packets_outgoing", metrics.packets_outgoing); block.AppendColumn("bits_incoming", metrics.bits_incoming); block.AppendColumn("bits_outgoing", metrics.bits_outgoing); block.AppendColumn("flows_incoming", metrics.flows_incoming); block.AppendColumn("flows_outgoing", metrics.flows_outgoing); register_clickhouse_per_protocol_metrics_block(block, metrics.pp); } // Registers metrics to block to push them into database void register_clickhouse_network_metrics_block(clickhouse::Block& block, ClickhouseNetworkMetrics& metrics) { block.AppendColumn("metricDateTime", metrics.date_time); block.AppendColumn("network", metrics.network); block.AppendColumn("packets_incoming", metrics.packets_incoming); block.AppendColumn("packets_outgoing", metrics.packets_outgoing); block.AppendColumn("bits_incoming", metrics.bits_incoming); block.AppendColumn("bits_outgoing", metrics.bits_outgoing); } // Push per host traffic counters to Clickhouse template // Apply limitation on type of keys because we use special string conversion function inside and we must not instantiate it for other unknown types requires(std::is_same_v || std::is_same_v) && (std::is_same_v)bool push_hosts_traffic_counters_to_clickhouse(clickhouse::Client* clickhouse_metrics_client, abstract_subnet_counters_t& host_counters, const std::string& table_name) { clickhouse::Block block; ClickhouseHostMetrics metrics; time_t seconds_since_epoch = time(NULL); uint64_t elements_in_dataset = 0; std::vector> speed_elements; // TODO: preallocate memory here for this array to avoid memory allocations under the lock host_counters.get_all_non_zero_average_speed_elements_as_pairs(speed_elements); for (const auto& speed_element : speed_elements) { std::string client_ip_as_string; if constexpr (std::is_same_v) { // We use pretty strange encoding here which encodes IPv6 address as subnet but // then we just discard CIDR mask because it does not matter client_ip_as_string = print_ipv6_address(speed_element.first.subnet_address); } else if constexpr (std::is_same_v) { // We use this encoding when we use client_ip_as_string = convert_ip_as_uint_to_string(speed_element.first); } else { logger << log4cpp::Priority::ERROR << "No match for push_hosts_traffic_counters_to_clickhouse"; return false; } const subnet_counter_t& current_speed_element = speed_element.second; // Skip elements with zero speed if (current_speed_element.is_zero()) { continue; } elements_in_dataset++; metrics.host->Append(client_ip_as_string); metrics.date_time->Append(seconds_since_epoch); // Populate Clickhouse metrics data using speed element data increment_clickhouse_host_counters(metrics, current_speed_element); } register_clickhouse_host_metrics_block(block, metrics); clickhouse_metrics_writes_total++; try { clickhouse_metrics_client->Insert(fastnetmon_global_configuration.clickhouse_metrics_database + "." + table_name, block); } catch (const std::exception& e) { clickhouse_metrics_writes_failed++; logger << log4cpp::Priority::DEBUG << "Failed to push " << elements_in_dataset << " host metrics to clickhouse: " << e.what(); return false; } catch (...) { clickhouse_metrics_writes_failed++; logger << log4cpp::Priority::DEBUG << "Failed to push " << elements_in_dataset << " host metrics to clickhouse"; return false; } return true; } class TotalMetricsElement { public: std::shared_ptr date_time{ nullptr }; std::shared_ptr direction{ nullptr }; std::shared_ptr flows{ nullptr }; std::shared_ptr packets{ nullptr }; std::shared_ptr bits{ nullptr }; std::shared_ptr tcp_packets{ nullptr }; std::shared_ptr udp_packets{ nullptr }; std::shared_ptr icmp_packets{ nullptr }; std::shared_ptr fragmented_packets{ nullptr }; std::shared_ptr tcp_syn_packets{ nullptr }; std::shared_ptr dropped_packets{ nullptr }; std::shared_ptr tcp_bits{ nullptr }; std::shared_ptr udp_bits{ nullptr }; std::shared_ptr icmp_bits{ nullptr }; std::shared_ptr fragmented_bits{ nullptr }; std::shared_ptr tcp_syn_bits{ nullptr }; std::shared_ptr dropped_bits{ nullptr }; }; // Push total counters to Clickhouse bool push_total_traffic_counters_to_clickhouse(clickhouse::Client* clickhouse_metrics_client, const total_speed_counters_t& total_counters, const std::string& table_name, bool ipv6) { extern uint64_t incoming_total_flows_speed; extern uint64_t outgoing_total_flows_speed; clickhouse::Block block; time_t seconds_since_epoch = time(NULL); TotalMetricsElement metrics{}; metrics.direction = std::make_shared(); metrics.date_time = std::make_shared(); metrics.flows = std::make_shared(); metrics.packets = std::make_shared(); metrics.bits = std::make_shared(); metrics.tcp_packets = std::make_shared(); metrics.udp_packets = std::make_shared(); metrics.icmp_packets = std::make_shared(); metrics.fragmented_packets = std::make_shared(); metrics.tcp_syn_packets = std::make_shared(); metrics.dropped_packets = std::make_shared(); metrics.tcp_bits = std::make_shared(); metrics.udp_bits = std::make_shared(); metrics.icmp_bits = std::make_shared(); metrics.fragmented_bits = std::make_shared(); metrics.tcp_syn_bits = std::make_shared(); metrics.dropped_bits = std::make_shared(); std::vector directions = { INCOMING, OUTGOING, INTERNAL, OTHER }; for (auto packet_direction : directions) { metrics.date_time->Append(seconds_since_epoch); // We have flow information only for incoming and outgoing directions if (packet_direction == INCOMING or packet_direction == OUTGOING) { uint64_t flow_counter_for_this_direction = 0; if (ipv6) { // TODO: we do not calculate flow counters for IPv6 yet } else { if (packet_direction == INCOMING) { flow_counter_for_this_direction = incoming_total_flows_speed; } else { flow_counter_for_this_direction = outgoing_total_flows_speed; } } metrics.flows->Append(flow_counter_for_this_direction); } else { metrics.flows->Append(0); } metrics.packets->Append(total_counters.total_speed_average_counters[packet_direction].total.packets); metrics.bits->Append(total_counters.total_speed_average_counters[packet_direction].total.bytes * 8); // Per protocol counters metrics.tcp_packets->Append(total_counters.total_speed_average_counters[packet_direction].tcp.packets); metrics.udp_packets->Append(total_counters.total_speed_average_counters[packet_direction].udp.packets); metrics.icmp_packets->Append(total_counters.total_speed_average_counters[packet_direction].icmp.packets); metrics.fragmented_packets->Append(total_counters.total_speed_average_counters[packet_direction].fragmented.packets); metrics.tcp_syn_packets->Append(total_counters.total_speed_average_counters[packet_direction].tcp_syn.packets); metrics.dropped_packets->Append(total_counters.total_speed_average_counters[packet_direction].dropped.packets); metrics.tcp_bits->Append(total_counters.total_speed_average_counters[packet_direction].tcp.bytes * 8); metrics.udp_bits->Append(total_counters.total_speed_average_counters[packet_direction].udp.bytes * 8); metrics.icmp_bits->Append(total_counters.total_speed_average_counters[packet_direction].icmp.bytes * 8); metrics.fragmented_bits->Append(total_counters.total_speed_average_counters[packet_direction].fragmented.bytes * 8); metrics.tcp_syn_bits->Append(total_counters.total_speed_average_counters[packet_direction].tcp_syn.bytes * 8); metrics.dropped_bits->Append(total_counters.total_speed_average_counters[packet_direction].dropped.bytes * 8); std::string direction_as_string = get_direction_name(packet_direction); metrics.direction->Append(direction_as_string.c_str()); } block.AppendColumn("metricDateTime", metrics.date_time); block.AppendColumn("direction", metrics.direction); block.AppendColumn("flows", metrics.flows); block.AppendColumn("packets", metrics.packets); block.AppendColumn("bits", metrics.bits); // Per protocol block.AppendColumn("tcp_packets", metrics.tcp_packets); block.AppendColumn("udp_packets", metrics.udp_packets); block.AppendColumn("icmp_packets", metrics.icmp_packets); block.AppendColumn("fragmented_packets", metrics.fragmented_packets); block.AppendColumn("tcp_syn_packets", metrics.tcp_syn_packets); block.AppendColumn("dropped_packets", metrics.dropped_packets); block.AppendColumn("tcp_bits", metrics.tcp_bits); block.AppendColumn("udp_bits", metrics.udp_bits); block.AppendColumn("icmp_bits", metrics.icmp_bits); block.AppendColumn("fragmented_bits", metrics.fragmented_bits); block.AppendColumn("tcp_syn_bits", metrics.tcp_syn_bits); block.AppendColumn("dropped_bits", metrics.dropped_bits); clickhouse_metrics_writes_total++; try { clickhouse_metrics_client->Insert(fastnetmon_global_configuration.clickhouse_metrics_database + "." + table_name, block); } catch (const std::exception& e) { clickhouse_metrics_writes_failed++; logger << log4cpp::Priority::DEBUG << "Failed to push total metrics to clickhouse: " << e.what(); return false; } catch (...) { clickhouse_metrics_writes_failed++; logger << log4cpp::Priority::DEBUG << "Failed to push total metrics to clickhouse"; return false; } return true; } class SystemMetricsElement { public: std::shared_ptr date_time{ nullptr }; std::shared_ptr name{ nullptr }; std::shared_ptr value{ nullptr }; std::shared_ptr type{ nullptr }; }; // Push system counters to Clickhouse bool push_system_counters_to_clickhouse(clickhouse::Client* clickhouse_metrics_client) { clickhouse::Block block; time_t seconds_since_epoch = time(NULL); SystemMetricsElement metrics{}; metrics.name = std::make_shared(); metrics.date_time = std::make_shared(); metrics.value = std::make_shared(); metrics.type = std::make_shared(); std::vector system_counters; bool result = get_statistics(system_counters); if (!result) { logger << log4cpp::Priority::ERROR << "Can't collect system counters"; return false; } for (auto counter : system_counters) { metrics.date_time->Append(seconds_since_epoch); metrics.name->Append(counter.counter_name); metrics.value->Append(counter.counter_value); if (counter.counter_type == metric_type_t::counter) { metrics.type->Append("counter"); } else if (counter.counter_type == metric_type_t::gauge) { metrics.type->Append("gauge"); } else { metrics.type->Append("unknown"); } } block.AppendColumn("metricDateTime", metrics.date_time); block.AppendColumn("name", metrics.name); block.AppendColumn("value", metrics.value); block.AppendColumn("type", metrics.type); clickhouse_metrics_writes_total++; try { clickhouse_metrics_client->Insert(fastnetmon_global_configuration.clickhouse_metrics_database + "." + "system_metrics", block); } catch (const std::exception& e) { clickhouse_metrics_writes_failed++; logger << log4cpp::Priority::DEBUG << "Failed to push total metrics to clickhouse: " << e.what(); return false; } catch (...) { clickhouse_metrics_writes_failed++; logger << log4cpp::Priority::DEBUG << "Failed to push system metrics to clickhouse"; return false; } return true; } // Push per subnet traffic counters to Clickhouse template requires std::is_same_v bool push_network_traffic_counters_to_clickhouse(clickhouse::Client* clickhouse_metrics_client, abstract_subnet_counters_t& network_counters, const std::string& table_name) { clickhouse::Block block; ClickhouseNetworkMetrics metrics; time_t seconds_since_epoch = time(NULL); uint64_t elements_in_dataset = 0; std::vector> speed_elements; network_counters.get_all_non_zero_average_speed_elements_as_pairs(speed_elements); for (const auto& itr : speed_elements) { const subnet_counter_t& speed = itr.second; // This function can convert both IPv4 and IPv6 subnets to text format std::string subnet_as_string = convert_any_subnet_to_string(itr.first); metrics.date_time->Append(seconds_since_epoch); metrics.network->Append(subnet_as_string.c_str()); metrics.packets_incoming->Append(speed.total.in_packets); metrics.packets_outgoing->Append(speed.total.out_packets); metrics.bits_incoming->Append(speed.total.in_bytes * 8); metrics.bits_outgoing->Append(speed.total.out_bytes * 8); increment_clickhouse_per_protocol_counters(metrics.pp, speed); elements_in_dataset++; } register_clickhouse_network_metrics_block(block, metrics); // Per protocol metrics register_clickhouse_per_protocol_metrics_block(block, metrics.pp); clickhouse_metrics_writes_total++; try { clickhouse_metrics_client->Insert(fastnetmon_global_configuration.clickhouse_metrics_database + "." + table_name, block); } catch (const std::exception& e) { clickhouse_metrics_writes_failed++; logger << log4cpp::Priority::DEBUG << "Failed to push " << elements_in_dataset << " network metrics to clickhouse: " << e.what(); return false; } catch (...) { clickhouse_metrics_writes_failed++; logger << log4cpp::Priority::DEBUG << "Failed to push " << elements_in_dataset << " network metrics to clickhouse"; return false; } return true; } // We need this flag to avoid attempts to create Clickhouse tables on each iteration, we need do it only once bool clickhouse_tables_successfully_created = false; // This thread pushes data to Clickhouse void clickhouse_push_thread() { extern total_speed_counters_t total_counters; extern total_speed_counters_t total_counters_ipv4; extern total_speed_counters_t total_counters_ipv6; extern abstract_subnet_counters_t ipv4_network_counters; extern abstract_subnet_counters_t ipv6_network_counters; extern abstract_subnet_counters_t ipv4_host_counters; extern abstract_subnet_counters_t ipv6_host_counters; // Sleep less then 1 second to capture speed calculated for very first time by speed calculation logic boost::this_thread::sleep(boost::posix_time::milliseconds(700)); while (true) { // Client object for Clickhouse to push metrics clickhouse::Client* clickhouse_metrics_client = nullptr; // Connect to Clickhouse socket to push metrics logger << log4cpp::Priority::DEBUG << "Establish connection to Clickhouse to store metrics"; // Create ClickHouse connection auto client_options = clickhouse::ClientOptions() .SetHost(fastnetmon_global_configuration.clickhouse_metrics_host) .SetPort(fastnetmon_global_configuration.clickhouse_metrics_port) .SetUser(fastnetmon_global_configuration.clickhouse_metrics_username) .SetPassword(fastnetmon_global_configuration.clickhouse_metrics_password) .SetSendRetries(1) // We do not need retry logic here as we try to connect again and again on each new iteration .SetRetryTimeout(std::chrono::seconds(3)) .SetPingBeforeQuery(true) .SetRethrowException(true); try { clickhouse_metrics_client = new clickhouse::Client(client_options); } catch (const std::exception& ex) { logger << log4cpp::Priority::ERROR << "Could not connect to ClickHouse: " << ex.what(); // Each loop interruption must have similar sleep section boost::this_thread::sleep(boost::posix_time::seconds(fastnetmon_global_configuration.clickhouse_metrics_push_period)); continue; } catch (...) { logger << log4cpp::Priority::ERROR << "Could not connect to ClickHouse for some reasons"; // Each loop interruption must have similar sleep section boost::this_thread::sleep(boost::posix_time::seconds(fastnetmon_global_configuration.clickhouse_metrics_push_period)); continue; } logger << log4cpp::Priority::DEBUG << "Established connection with Clickhouse"; // We need to create tables only on first iteration if (!clickhouse_tables_successfully_created) { // Create database and tables auto clickhouse_init_res = create_clickhouse_tables_for_metrics(fastnetmon_global_configuration, clickhouse_metrics_client); if (!clickhouse_init_res) { logger << log4cpp::Priority::ERROR << "Could not create Clickhouse tables for metrics"; // Each loop interruption must have similar sleep section boost::this_thread::sleep(boost::posix_time::seconds(fastnetmon_global_configuration.clickhouse_metrics_push_period)); continue; } clickhouse_tables_successfully_created = true; } // Total traffic push_total_traffic_counters_to_clickhouse(clickhouse_metrics_client, total_counters, "total_metrics", false); // Total IPv4 traffic push_total_traffic_counters_to_clickhouse(clickhouse_metrics_client, total_counters_ipv4, "total_metrics_ipv4", false); // Total IPv6 traffic push_total_traffic_counters_to_clickhouse(clickhouse_metrics_client, total_counters_ipv6, "total_metrics_ipv6", true); // System counters push_system_counters_to_clickhouse(clickhouse_metrics_client); // Push per subnet counters to ClickHouse push_network_traffic_counters_to_clickhouse(clickhouse_metrics_client, ipv4_network_counters, "network_metrics"); push_network_traffic_counters_to_clickhouse(clickhouse_metrics_client, ipv6_network_counters, "network_metrics_ipv6"); // Push per host counters to ClickHouse push_hosts_traffic_counters_to_clickhouse(clickhouse_metrics_client, ipv4_host_counters, "host_metrics"); push_hosts_traffic_counters_to_clickhouse(clickhouse_metrics_client, ipv6_host_counters, "host_metrics_ipv6"); boost::this_thread::sleep(boost::posix_time::seconds(fastnetmon_global_configuration.clickhouse_metrics_push_period)); // It's not very clear that destructor for clickhouse::Client actually exists but we need to clear memory for // object at least I did tests and confirmed that we do not have fd leaks with this logic delete clickhouse_metrics_client; } } pavel-odintsov-fastnetmon-394fbe0/src/metrics/clickhouse.hpp000066400000000000000000000007551520703010000243500ustar00rootroot00000000000000#pragma once #include #include "../fastnetmon_types.hpp" #include "../fastnetmon_configuration_scheme.hpp" void clickhouse_push_thread(); bool push_network_traffic_counters_to_clickhouse(); bool push_total_traffic_counters_to_clickhouse(); bool push_hosts_traffic_counters_to_clickhouse(); bool init_clickhouse_for_metrics(fastnetmon_configuration_t& fastnetmon_global_configuration, clickhouse::Client*& clickhouse_metrics_client); pavel-odintsov-fastnetmon-394fbe0/src/metrics/graphite.cpp000066400000000000000000000221131520703010000240050ustar00rootroot00000000000000#include "graphite.hpp" #include "../abstract_subnet_counters.hpp" #include "../fast_library.hpp" #include "../fastnetmon_types.hpp" #include "../fastnetmon_configuration_scheme.hpp" #include extern log4cpp::Category& logger; extern fastnetmon_configuration_t fastnetmon_global_configuration; // Push host traffic to Graphite bool push_hosts_traffic_counters_to_graphite() { extern abstract_subnet_counters_t ipv4_host_counters; std::vector processed_directions = { INCOMING, OUTGOING }; graphite_data_t graphite_data; std::vector> speed_elements; ipv4_host_counters.get_all_non_zero_average_speed_elements_as_pairs(speed_elements); for (const auto& speed_element : speed_elements) { std::string client_ip_as_string = convert_ip_as_uint_to_string(speed_element.first); const subnet_counter_t* current_speed_element = &speed_element.second; // Skip elements with zero speed if (current_speed_element->is_zero()) { continue; } std::string ip_as_string_with_dash_delimiters = client_ip_as_string; // Replace dots by dashes std::replace(ip_as_string_with_dash_delimiters.begin(), ip_as_string_with_dash_delimiters.end(), '.', '_'); for (auto data_direction : processed_directions) { std::string direction_as_string; if (data_direction == INCOMING) { direction_as_string = "incoming"; } else if (data_direction == OUTGOING) { direction_as_string = "outgoing"; } std::string graphite_current_prefix = fastnetmon_global_configuration.graphite_prefix + ".hosts." + ip_as_string_with_dash_delimiters + "." + direction_as_string; if (data_direction == INCOMING) { // Prepare incoming traffic data // We do not store zero data to Graphite if (current_speed_element->total.in_packets != 0) { graphite_data[graphite_current_prefix + ".pps"] = current_speed_element->total.in_packets; } if (current_speed_element->total.in_bytes != 0) { graphite_data[graphite_current_prefix + ".bps"] = current_speed_element->total.in_bytes * 8; } if (current_speed_element->in_flows != 0) { graphite_data[graphite_current_prefix + ".flows"] = current_speed_element->in_flows; } } else if (data_direction == OUTGOING) { // Prepare outgoing traffic data // We do not store zero data to Graphite if (current_speed_element->total.out_packets != 0) { graphite_data[graphite_current_prefix + ".pps"] = current_speed_element->total.out_packets; } if (current_speed_element->total.out_bytes != 0) { graphite_data[graphite_current_prefix + ".bps"] = current_speed_element->total.out_bytes * 8; } if (current_speed_element->out_flows != 0) { graphite_data[graphite_current_prefix + ".flows"] = current_speed_element->out_flows; } } } } bool graphite_put_result = store_data_to_graphite(fastnetmon_global_configuration.graphite_port, fastnetmon_global_configuration.graphite_host, graphite_data); if (!graphite_put_result) { logger << log4cpp::Priority::ERROR << "Can't store host load data to Graphite server " << fastnetmon_global_configuration.graphite_host << " port: " << fastnetmon_global_configuration.graphite_port; return false; } return true; } // Push total counters to graphite bool push_total_traffic_counters_to_graphite() { extern total_speed_counters_t total_counters_ipv4; extern uint64_t incoming_total_flows_speed; extern uint64_t outgoing_total_flows_speed; std::vector directions = { INCOMING, OUTGOING, INTERNAL, OTHER }; for (auto packet_direction : directions) { 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; graphite_data_t graphite_data; std::string direction_as_string = get_direction_name(packet_direction); // We have flow information only for incoming and outgoing directions if (packet_direction == INCOMING or packet_direction == OUTGOING) { uint64_t flow_counter_for_this_direction = 0; if (packet_direction == INCOMING) { flow_counter_for_this_direction = incoming_total_flows_speed; } else { flow_counter_for_this_direction = outgoing_total_flows_speed; } graphite_data[fastnetmon_global_configuration.graphite_prefix + ".total." + direction_as_string + ".flows"] = flow_counter_for_this_direction; } graphite_data[fastnetmon_global_configuration.graphite_prefix + ".total." + direction_as_string + ".pps"] = speed_in_pps; graphite_data[fastnetmon_global_configuration.graphite_prefix + ".total." + direction_as_string + ".bps"] = speed_in_bps * 8; bool graphite_put_result = store_data_to_graphite(fastnetmon_global_configuration.graphite_port, fastnetmon_global_configuration.graphite_host, graphite_data); if (!graphite_put_result) { logger << log4cpp::Priority::ERROR << "Can't store total load data to Graphite server " << fastnetmon_global_configuration.graphite_host << " port: " << fastnetmon_global_configuration.graphite_port; return false; } } return true; } // Push per subnet traffic counters to graphite bool push_network_traffic_counters_to_graphite() { extern abstract_subnet_counters_t ipv4_network_counters; graphite_data_t graphite_data; std::vector> speed_elements; ipv4_network_counters.get_all_non_zero_average_speed_elements_as_pairs(speed_elements); for (const auto& itr : speed_elements) { const subnet_counter_t* speed = &itr.second; std::string subnet_as_string_as_dash_delimiters = convert_ipv4_subnet_to_string(itr.first); // Replace dots by dashes std::replace(subnet_as_string_as_dash_delimiters.begin(), subnet_as_string_as_dash_delimiters.end(), '.', '_'); // Replace / by dashes too std::replace(subnet_as_string_as_dash_delimiters.begin(), subnet_as_string_as_dash_delimiters.end(), '/', '_'); std::string current_prefix = fastnetmon_global_configuration.graphite_prefix + ".networks." + subnet_as_string_as_dash_delimiters + "."; graphite_data[current_prefix + "incoming.pps"] = speed->total.in_packets; graphite_data[current_prefix + "outgoing.pps"] = speed->total.out_packets; graphite_data[current_prefix + "incoming.bps"] = speed->total.in_bytes * 8; graphite_data[current_prefix + "outgoing.bps"] = speed->total.out_bytes * 8; } bool graphite_put_result = store_data_to_graphite(fastnetmon_global_configuration.graphite_port, fastnetmon_global_configuration.graphite_host, graphite_data); if (!graphite_put_result) { logger << log4cpp::Priority::ERROR << "Can't store network load data to Graphite server " << fastnetmon_global_configuration.graphite_host << " port: " << fastnetmon_global_configuration.graphite_port; return false; } return true; } // This thread pushes speed counters to graphite void graphite_push_thread() { extern struct timeval graphite_thread_execution_time; // Sleep less then 1 second to capture speed calculated for very first time by speed calculation logic boost::this_thread::sleep(boost::posix_time::milliseconds(700)); while (true) { struct timeval start_calc_time; gettimeofday(&start_calc_time, NULL); // First of all push total counters to Graphite push_total_traffic_counters_to_graphite(); // Push per subnet counters to graphite push_network_traffic_counters_to_graphite(); // Push per host counters to graphite push_hosts_traffic_counters_to_graphite(); struct timeval end_calc_time; gettimeofday(&end_calc_time, NULL); timeval_subtract(&graphite_thread_execution_time, &end_calc_time, &start_calc_time); logger << log4cpp::Priority::DEBUG << "Graphite data pushed in: " << graphite_thread_execution_time.tv_sec << " sec " << graphite_thread_execution_time.tv_usec << " microseconds\n"; boost::this_thread::sleep(boost::posix_time::seconds(fastnetmon_global_configuration.graphite_push_period)); } } pavel-odintsov-fastnetmon-394fbe0/src/metrics/graphite.hpp000066400000000000000000000002751520703010000240170ustar00rootroot00000000000000#pragma once void graphite_push_thread(); bool push_total_traffic_counters_to_graphite(); bool push_network_traffic_counters_to_graphite(); bool push_hosts_traffic_counters_to_graphite(); pavel-odintsov-fastnetmon-394fbe0/src/metrics/influxdb.cpp000066400000000000000000000650271520703010000240300ustar00rootroot00000000000000#include "influxdb.hpp" #include "../abstract_subnet_counters.hpp" #include "../fast_library.hpp" #include "../fastnetmon_types.hpp" #include "../all_logcpp_libraries.hpp" #include "../abstract_subnet_counters.hpp" #include "../fastnetmon_configuration_scheme.hpp" #include extern struct timeval graphite_thread_execution_time; extern uint64_t influxdb_writes_total; extern uint64_t influxdb_writes_failed; extern log4cpp::Category& logger; extern fastnetmon_configuration_t fastnetmon_global_configuration; // I do this declaration here to avoid circular dependencies between fastnetmon_logic and this file bool get_statistics(std::vector& system_counters); bool write_batch_of_data_to_influxdb(const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& measurement, const std::string& tag_name, const std::vector>>& hosts_vector, std::string& error_text); // Set block of data into InfluxDB bool write_line_of_data_to_influxdb(const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& measurement, const std::map& tags, const std::map& plain_total_counters_map, std::string& error_text); // Prepare string to insert data into InfluxDB std::string craft_line_for_influxdb_line_protocol(uint64_t unix_timestamp_nanoseconds, const std::string& measurement, const std::map& tags, const std::map& plain_total_counters_map); void fill_fixed_counters_for_influxdb(const subnet_counter_t& counter, std::map& plain_total_counters_map, bool populate_flow); bool push_system_counters_to_influxdb(const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password); bool push_total_traffic_counters_to_influxdb(const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& measurement_name, total_counter_element_t total_speed_average_counters_param[4], bool ipv6); // Push system counters to InfluxDB bool push_system_counters_to_influxdb(const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password) { std::vector system_counters; bool result = get_statistics(system_counters); if (!result) { logger << log4cpp::Priority::ERROR << "Can't collect system counters"; return false; } std::map plain_total_counters_map; for (auto counter : system_counters) { plain_total_counters_map[counter.counter_name] = counter.counter_value; } influxdb_writes_total++; std::map tags = { { "metric", "metric_value" } }; std::string error_text; bool influx_result = write_line_of_data_to_influxdb(influx_database, influx_host, influx_port, enable_auth, influx_user, influx_password, "system_counters", tags, plain_total_counters_map, error_text); if (!influx_result) { influxdb_writes_failed++; logger << log4cpp::Priority::DEBUG << "InfluxDB write operation failed for system counters with error " << error_text; return false; } return true; } // Push network traffic to InfluxDB template requires std::is_same_v bool push_network_traffic_counters_to_influxdb(abstract_subnet_counters_t& network_counters, const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& measurement, const std::string& tag_name) { std::vector> speed_elements; // Retrieve copy of all counters network_counters.get_all_non_zero_average_speed_elements_as_pairs(speed_elements); // Structure for InfluxDB std::vector>> networks_vector; for (const auto& speed_element : speed_elements) { std::map plain_total_counters_map; // This function can convert both IPv4 and IPv6 subnets to text format std::string network_as_cidr_string = convert_any_subnet_to_string(speed_element.first); fill_fixed_counters_for_influxdb(speed_element.second, plain_total_counters_map, true); networks_vector.push_back(std::make_pair(network_as_cidr_string, plain_total_counters_map)); } if (networks_vector.size() > 0) { influxdb_writes_total++; std::string error_text; bool result = write_batch_of_data_to_influxdb(influx_database, influx_host, influx_port, enable_auth, influx_user, influx_password, measurement, tag_name, networks_vector, error_text); if (!result) { influxdb_writes_failed++; logger << log4cpp::Priority::DEBUG << "InfluxDB batch operation failed for " << measurement << " with error " << error_text; return false; } } return true; } // Push total traffic counters to InfluxDB bool push_total_traffic_counters_to_influxdb(const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& measurement_name, total_counter_element_t total_speed_average_counters_param[4], bool ipv6) { extern uint64_t incoming_total_flows_speed; extern uint64_t outgoing_total_flows_speed; std::vector directions = { INCOMING, OUTGOING, INTERNAL, OTHER }; for (auto packet_direction : directions) { std::map plain_total_counters_map; // We do not have this counter for IPv6 if (!ipv6) { // We have flow information only for incoming and outgoing directions if (packet_direction == INCOMING or packet_direction == OUTGOING) { uint64_t flow_counter_for_this_direction = 0; if (packet_direction == INCOMING) { flow_counter_for_this_direction = incoming_total_flows_speed; } else { flow_counter_for_this_direction = outgoing_total_flows_speed; } plain_total_counters_map["flows"] = flow_counter_for_this_direction; } } plain_total_counters_map["packets"] = total_speed_average_counters_param[packet_direction].total.packets; plain_total_counters_map["bits"] = total_speed_average_counters_param[packet_direction].total.bytes * 8; plain_total_counters_map["udp_packets"] = total_speed_average_counters_param[packet_direction].udp.packets; plain_total_counters_map["udp_bits"] = total_speed_average_counters_param[packet_direction].udp.bytes * 8; plain_total_counters_map["tcp_packets"] = total_speed_average_counters_param[packet_direction].tcp.packets; plain_total_counters_map["tcp_bits"] = total_speed_average_counters_param[packet_direction].tcp.bytes * 8; plain_total_counters_map["icmp_packets"] = total_speed_average_counters_param[packet_direction].icmp.packets; plain_total_counters_map["icmp_bits"] = total_speed_average_counters_param[packet_direction].icmp.bytes * 8; plain_total_counters_map["fragmented_packets"] = total_speed_average_counters_param[packet_direction].fragmented.packets; plain_total_counters_map["fragmented_bits"] = total_speed_average_counters_param[packet_direction].fragmented.bytes * 8; plain_total_counters_map["tcp_syn_packets"] = total_speed_average_counters_param[packet_direction].tcp_syn.packets; plain_total_counters_map["tcp_syn_bits"] = total_speed_average_counters_param[packet_direction].tcp_syn.bytes * 8; plain_total_counters_map["dropped_packets"] = total_speed_average_counters_param[packet_direction].dropped.packets; plain_total_counters_map["dropped_bits"] = total_speed_average_counters_param[packet_direction].dropped.bytes * 8; std::string direction_as_string = get_direction_name(packet_direction); influxdb_writes_total++; std::map tags = { { "direction", direction_as_string } }; std::string error_text; bool result = write_line_of_data_to_influxdb(influx_database, influx_host, influx_port, enable_auth, influx_user, influx_password, measurement_name, tags, plain_total_counters_map, error_text); if (!result) { influxdb_writes_failed++; logger << log4cpp::Priority::DEBUG << "InfluxDB write operation failed for total_traffic with error: " << error_text; return false; } } return true; } // Push host traffic to InfluxDB template // Apply limitation on type of keys because we use special string conversion function inside and we must not instantiate it for other unknown types requires(std::is_same_v || std::is_same_v) && (std::is_same_v)bool push_hosts_traffic_counters_to_influxdb(abstract_subnet_counters_t& host_counters, const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& measurement, const std::string& tag_name) { std::vector> speed_elements; // TODO: preallocate memory here for this array to avoid memory allocations under the lock host_counters.get_all_non_zero_average_speed_elements_as_pairs(speed_elements); // Structure for InfluxDB std::vector>> hosts_vector; for (const auto& speed_element : speed_elements) { std::map plain_total_counters_map; std::string client_ip_as_string; if constexpr (std::is_same_v) { // We use pretty strange encoding here which encodes IPv6 address as subnet but // then we just discard CIDR mask because it does not matter client_ip_as_string = print_ipv6_address(speed_element.first.subnet_address); } else if constexpr (std::is_same_v) { // We use this encoding when we use client_ip_as_string = convert_ip_as_uint_to_string(speed_element.first); } else { logger << log4cpp::Priority::ERROR << "No match for push_hosts_traffic_counters_to_influxdb"; return false; } fill_fixed_counters_for_influxdb(speed_element.second, plain_total_counters_map, true); hosts_vector.push_back(std::make_pair(client_ip_as_string, plain_total_counters_map)); } // TODO: For big networks it will cause HUGE batches, it will make sense to split them in 5-10k batches if (hosts_vector.size() > 0) { influxdb_writes_total++; std::string error_text; bool result = write_batch_of_data_to_influxdb(influx_database, influx_host, influx_port, enable_auth, influx_user, influx_password, measurement, tag_name, hosts_vector, error_text); if (!result) { influxdb_writes_failed++; logger << log4cpp::Priority::DEBUG << "InfluxDB batch operation failed for hosts_traffic with error " << error_text; return false; } } return true; } // This thread pushes data to InfluxDB void influxdb_push_thread() { extern abstract_subnet_counters_t ipv6_host_counters; extern abstract_subnet_counters_t ipv6_network_counters; extern abstract_subnet_counters_t ipv4_host_counters; extern total_speed_counters_t total_counters_ipv4; extern total_speed_counters_t total_counters_ipv6; extern abstract_subnet_counters_t ipv4_network_counters; extern abstract_subnet_counters_t ipv4_network_24_counters; std::string influx_database = fastnetmon_global_configuration.influxdb_database; std::string influx_host = fastnetmon_global_configuration.influxdb_host; std::string influx_port = std::to_string(fastnetmon_global_configuration.influxdb_port); bool enable_auth = fastnetmon_global_configuration.influxdb_auth; std::string influx_user = fastnetmon_global_configuration.influxdb_user; std::string influx_password = fastnetmon_global_configuration.influxdb_password; bool do_dns_resolution = false; // If address does not look like IPv4 or IPv6 then we will use DNS resolution for it if (!validate_ipv6_or_ipv4_host(influx_host)) { logger << log4cpp::Priority::INFO << "You set InfluxDB server address as hostname " << influx_host << " and we will use DNS to resolve it"; do_dns_resolution = true; } // Sleep less then 1 second to capture speed calculated for very first time by speed calculation logic boost::this_thread::sleep(boost::posix_time::milliseconds(700)); while (true) { std::string current_influxdb_ip_address = ""; if (do_dns_resolution) { std::string ip_address = dns_lookup(influx_host); if (ip_address.empty()) { logger << log4cpp::Priority::ERROR << "Cannot resolve " << influx_host << " to address"; // Each loop interruption must have similar sleep section boost::this_thread::sleep(boost::posix_time::seconds(fastnetmon_global_configuration.influxdb_push_period)); continue; } logger << log4cpp::Priority::DEBUG << "Resolved " << influx_host << " to " << ip_address; current_influxdb_ip_address = ip_address; } else { // We do not need DNS resolution here, use address as is current_influxdb_ip_address = fastnetmon_global_configuration.influxdb_host; } // First of all push total counters to InfluxDB push_total_traffic_counters_to_influxdb(influx_database, current_influxdb_ip_address, influx_port, enable_auth, influx_user, influx_password, "total_traffic", total_counters_ipv4.total_speed_average_counters, false); // Push per subnet counters to InfluxDB push_network_traffic_counters_to_influxdb(ipv4_network_counters, influx_database, current_influxdb_ip_address, influx_port, enable_auth, influx_user, influx_password, "networks_traffic", "network"); // Push per host counters to InfluxDB push_hosts_traffic_counters_to_influxdb(ipv4_host_counters, influx_database, current_influxdb_ip_address, influx_port, enable_auth, influx_user, influx_password, "hosts_traffic", "host"); push_system_counters_to_influxdb(influx_database, current_influxdb_ip_address, influx_port, enable_auth, influx_user, influx_password); // Push per host IPv6 counters to InfluxDB push_hosts_traffic_counters_to_influxdb(ipv6_host_counters, influx_database, current_influxdb_ip_address, influx_port, enable_auth, influx_user, influx_password, "hosts_ipv6_traffic", "host"); // Push per network IPv6 counters to InfluxDB push_network_traffic_counters_to_influxdb(ipv6_network_counters, influx_database, current_influxdb_ip_address, influx_port, enable_auth, influx_user, influx_password, "networks_ipv6_traffic", "network"); // Push total IPv6 counters push_total_traffic_counters_to_influxdb(influx_database, current_influxdb_ip_address, influx_port, enable_auth, influx_user, influx_password, "total_traffic_ipv6", total_counters_ipv6.total_speed_average_counters, true); boost::this_thread::sleep(boost::posix_time::seconds(fastnetmon_global_configuration.influxdb_push_period)); } } // Write batch of data for particular InfluxDB database bool write_batch_of_data_to_influxdb(const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& measurement, const std::string& tag_name, const std::vector>>& hosts_vector, std::string& error_text) { // Nothing to write if (hosts_vector.size() == 0) { return true; } std::stringstream buffer; uint64_t unix_timestamp_nanoseconds = get_current_unix_time_in_nanoseconds(); // Prepare batch for insert for (auto& host_traffic : hosts_vector) { std::map tags = { { tag_name, host_traffic.first } }; std::string line_protocol_format = craft_line_for_influxdb_line_protocol(unix_timestamp_nanoseconds, measurement, tags, host_traffic.second); buffer << line_protocol_format << "\n"; } // logger << log4cpp::Priority::INFO << "Raw data to InfluxDB: " << buffer.str(); return write_data_to_influxdb(influx_database, influx_host, influx_port, enable_auth, influx_user, influx_password, buffer.str(), error_text); } // Set block of data into InfluxDB bool write_line_of_data_to_influxdb(const std::string& influx_database, const std::string& influx_host, const std::string& influx_port, bool enable_auth, const std::string& influx_user, const std::string& influx_password, const std::string& measurement, const std::map& tags, const std::map& plain_total_counters_map, std::string& error_text) { uint64_t unix_timestamp_nanoseconds = get_current_unix_time_in_nanoseconds(); auto influxdb_line = craft_line_for_influxdb_line_protocol(unix_timestamp_nanoseconds, measurement, tags, plain_total_counters_map); // logger << log4cpp::Priority::INFO << "Raw data to InfluxDB: " << buffer.str(); return write_data_to_influxdb(influx_database, influx_host, influx_port, enable_auth, influx_user, influx_password, influxdb_line, error_text); } // Simple helper function to add additional metrics easily void add_counter_to_influxdb(std::map& plain_total_counters_map, const traffic_counter_element_t& counter, const std::string& counter_name) { plain_total_counters_map[counter_name + "_packets_incoming"] = counter.in_packets; plain_total_counters_map[counter_name + "_bits_incoming"] = counter.in_bytes * 8; plain_total_counters_map[counter_name + "_packets_outgoing"] = counter.out_packets; plain_total_counters_map[counter_name + "_bits_outgoing"] = counter.out_bytes * 8; } // Fills special structure which we use to export metrics into InfluxDB void fill_per_protocol_countres_for_influxdb(const subnet_counter_t& current_speed_element, std::map& plain_total_counters_map) { add_counter_to_influxdb(plain_total_counters_map, current_speed_element.dropped, "dropped"); add_counter_to_influxdb(plain_total_counters_map, current_speed_element.fragmented, "fragmented"); add_counter_to_influxdb(plain_total_counters_map, current_speed_element.tcp, "tcp"); add_counter_to_influxdb(plain_total_counters_map, current_speed_element.tcp_syn, "tcp_syn"); add_counter_to_influxdb(plain_total_counters_map, current_speed_element.udp, "udp"); add_counter_to_influxdb(plain_total_counters_map, current_speed_element.icmp, "icmp"); } // Fills special structure which we use to export metrics into InfluxDB void fill_main_counters_for_influxdb(const subnet_counter_t& current_speed_element, std::map& plain_total_counters_map, bool populate_flow) { // Prepare incoming traffic data plain_total_counters_map["packets_incoming"] = current_speed_element.total.in_packets; plain_total_counters_map["bits_incoming"] = current_speed_element.total.in_bytes * 8; // Outdoing traffic plain_total_counters_map["packets_outgoing"] = current_speed_element.total.out_packets; plain_total_counters_map["bits_outgoing"] = current_speed_element.total.out_bytes * 8; if (populate_flow) { plain_total_counters_map["flows_incoming"] = current_speed_element.in_flows; plain_total_counters_map["flows_outgoing"] = current_speed_element.out_flows; } } // Fills counters for standard fixed counters void fill_fixed_counters_for_influxdb(const subnet_counter_t& counter, std::map& plain_total_counters_map, bool populate_flow) { fill_main_counters_for_influxdb(counter, plain_total_counters_map, populate_flow); fill_per_protocol_countres_for_influxdb(counter, plain_total_counters_map); return; } // Prepare string to insert data into InfluxDB std::string craft_line_for_influxdb_line_protocol(uint64_t unix_timestamp_nanoseconds, const std::string& measurement, const std::map& tags, const std::map& plain_total_counters_map) { std::stringstream buffer; buffer << measurement << ","; // tag set section buffer << join_by_comma_and_equal(tags); buffer << " "; // field set section for (auto itr = plain_total_counters_map.begin(); itr != plain_total_counters_map.end(); ++itr) { buffer << itr->first << "=" << std::to_string(itr->second); // it's last element if (std::distance(itr, plain_total_counters_map.end()) == 1) { // Do not print comma } else { buffer << ","; } } buffer << " " << std::to_string(unix_timestamp_nanoseconds); return buffer.str(); } pavel-odintsov-fastnetmon-394fbe0/src/metrics/influxdb.hpp000066400000000000000000000003231520703010000240210ustar00rootroot00000000000000#pragma once #include #include #include "../fastnetmon_types.hpp" void send_grafana_alert(std::string title, std::string text, std::vector& tags); void influxdb_push_thread(); pavel-odintsov-fastnetmon-394fbe0/src/mikrotik_plugin/000077500000000000000000000000001520703010000232405ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/mikrotik_plugin/README.md000066400000000000000000000027731520703010000245300ustar00rootroot00000000000000MikroTik FastNetMon plug-in =========== Overview -------- Connects to a MikroTik router and adds or removes a blackhole rule for an attack by IP address. The actions can be modified such as adding a firewall rule. This script uses the MikroTik PHP API. More information about this can be found at the following URLs: * http://www.mikrotik.com * http://wiki.mikrotik.com/wiki/API_PHP_class Installation ------------ #### Prerequisite You must have a user with API access on the router Install php to your server: ``` sudo apt-get install php-cli php ``` #### Process 1. Configure the router in the ```fastnetmon_mikrotik.php``` file ``` $cfg[ ip_mikrotik ] = "192.168.10.1"; // MikroTik Router IP $cfg[ api_user ] = "api"; // username $cfg[ api_pass ] = "api123"; // password ``` 2. Change the ```notify_about_attack.sh``` with the new to run the PHP script This is the first version, you are welcome to add more features. 3. Set executable bit ```sudo chmod +x /etc/fastnetmon/scripts/notify_about_attack.sh``` 4. For FastNetMon Advanced, specify this script as callback: ``` sudo fcli set main notify_script_enabled enable sudo fcli set main notify_script_path /etc/fastnetmon/scripts/notify_about_attack.sh sudo fcli set main notify_script_format text sudo fcli commit ``` Changelog --------- v1.1 - 12 Oct 2020 - fix RouterOS API-ssl to support post MikroTik 6.45.1 v1.0 - 4 Jul 16 - Initial version Author: Maximiliano Dobladez info@mkesolutions.net http://maxid.com.ar | http://www.mkesolutions.net pavel-odintsov-fastnetmon-394fbe0/src/mikrotik_plugin/fastnetmon_mikrotik.php000066400000000000000000000122601520703010000300410ustar00rootroot00000000000000#!/usr/bin/php 1 && $argv[ 1 ] == "help" ) { $msg = "MikroTik's API Integration for FastNetMon - Ver: " . _VER; echo $msg; exit(1); } /* This script will get following params from FastNetMon: $1 client_ip_as_string $2 data_direction $3 pps_as_string $4 action (ban or unban) */ // Ensure that we got all required arguments if ( $argc <= 4 ) { $msg = "MikroTik's API Integration for FastNetMon - Ver: " . _VER . "\n"; $msg .= "missing arguments"; $msg .= "php fastnetmon_mikrotik.php [IP] [data_direction] [pps_as_string] [action] \n"; _log( $msg ); echo $msg; exit( 1 ); } // IPv4 or IPv6 address of attack $IP_ATTACK = $argv[ 1 ]; if ( filter_var( $IP_ATTACK, FILTER_VALIDATE_IP ) === false ) { $msg = "Invalid IP address: " . $IP_ATTACK; _log( $msg ); echo $msg . "\n"; exit( 1 ); } // incoming, outgoing or unknown $DIRECTION_ATTACK = $argv[ 2 ]; if ( !in_array( $DIRECTION_ATTACK, [ 'incoming', 'outgoing', 'unknown' ], true ) ) { $msg = "Invalid direction: " . $DIRECTION_ATTACK . ". Must be incoming, outgoing, or unknown."; _log( $msg ); echo $msg . "\n"; exit( 1 ); } // Power of attack in packets per second $POWER_ATTACK = $argv[ 3 ]; if ( !ctype_digit( $POWER_ATTACK ) ) { $msg = "Invalid pps value: " . $POWER_ATTACK . ". Must be a positive integer."; _log( $msg ); echo $msg . "\n"; exit( 1 ); } // Action: ban or unban $ACTION_ATTACK = $argv[ 4 ]; if ( !in_array( $ACTION_ATTACK, [ 'ban', 'unban' ], true ) ) { $msg = "Invalid action: " . $ACTION_ATTACK . ". Must be ban or unban."; _log( $msg ); echo $msg . "\n"; exit( 1 ); } require_once "routeros_api.php"; $API = new RouterosAPI(); // $API->debug = true; if (! $API->connect( $cfg[ 'ip_mikrotik' ], $cfg[ 'api_user' ], $cfg[ 'api_pass' ] ) ) { // can't connect $msg = "Couldn't connect to " . $cfg[ 'ip_mikrotik' ]; _log( $msg ); echo $msg; exit( 1 ); } $time_now = date("Y-m-d H:i:s", time()); //add Blocking by route blackhole if ( $ACTION_ATTACK == "ban" ) { $comment_rule = 'FastNetMon Community: IP ' . $IP_ATTACK . ' blocked because ' . $DIRECTION_ATTACK . ' attack with power ' . $POWER_ATTACK . ' pps | at '.$time_now; $API->write( '/ip/route/add', false ); $API->write( '=dst-address=' . $IP_ATTACK, false ); $API->write( '=type=blackhole', false ); $API->write( '=bgp-communities=65535:666', false ); $API->write( '=comment=' . $comment_rule ); // Log to router syslog. Useful for alerting and Graylog reporting $API->write( '/log/info', false ); $API->write( '=message=' . $comment_rule ); $ret = $API->read(); if ($ret) _log( $comment_rule ); } elseif ($ACTION_ATTACK == "unban" ) { // remove the blackhole rule $comment_rule = 'FastNetMon Community: IP ' . $IP_ATTACK . ' remove from blacklist '; $API->write( '/ip/route/print', false ); $API->write( '?dst-address=' . $IP_ATTACK . "/32" ); $ID_ARRAY = $API->read(); $API->write( '/ip/route/remove', false ); $API->write( '=.id=' . $ID_ARRAY[ 0 ][ '.id' ] ); // Log to router syslog. Useful for alerting and Graylog reporting $API->write( '/log/info', false ); $API->write( '=message=' . $comment_rule ); $ret = $API->read(); if ($ret) _log( $comment_rule ); } else { $msg = "Unknown action: " . $ACTION_ATTACK; _log( $msg ); echo $msg; exit( 1 ); } /** * [_log Write a log file] * @param [type] $msg [text to log] * @return [type] */ function _log( $msg ) { $FILE_LOG_TMP = "/tmp/fastnetmon_api_mikrotik.log"; $line = date("D M j H:i:s T Y") . " - [FASTNETMON] - " . $msg . "\n"; file_put_contents( $FILE_LOG_TMP, $line, FILE_APPEND ); } ?> pavel-odintsov-fastnetmon-394fbe0/src/mikrotik_plugin/notify_about_attack.sh000077500000000000000000000005451520703010000276340ustar00rootroot00000000000000#!/usr/bin/env bash # # Fastnetmon: MikroTik RouterOS plugin # # by Maximiliano Dobladez - info@mkesolutions.net - http://maxid.com.ar # # This script will get following params: # $1 client_ip_as_string # $2 data_direction # $3 pps_as_string # $4 action (ban or unban) php -f /opt/fastnetmon/fastnetmon_mikrotik.php $1 $2 $3 $4 exit 0 pavel-odintsov-fastnetmon-394fbe0/src/mikrotik_plugin/routeros_api.php000066400000000000000000000360421520703010000264710ustar00rootroot00000000000000 * Nick Barnes * Ben Menking (ben [at] infotechsc [dot] com) * Jeremy Jefferson (http://jeremyj.com) * Cristian Deluxe (djcristiandeluxe [at] gmail [dot] com) * Mikhail Moskalev (mmv.rus [at] gmail [dot] com) * * http://www.mikrotik.com * http://wiki.mikrotik.com/wiki/API_PHP_class * ******************************/ class RouterosAPI { var $debug = false; // Show debug information var $connected = false; // Connection state var $port = 8728; // Port to connect to (default 8729 for ssl) var $ssl = false; // Connect using SSL (must enable api-ssl in IP/Services) var $timeout = 3; // Connection attempt timeout and data read timeout var $attempts = 1; // Connection attempt count var $delay = 2; // Delay between connection attempts in seconds var $socket; // Variable for storing socket resource var $error_no; // Variable for storing connection error number, if any var $error_str; // Variable for storing connection error text, if any /* Check, can be var used in foreach */ public function isIterable($var) { return $var !== null && (is_array($var) || $var instanceof Traversable || $var instanceof Iterator || $var instanceof IteratorAggregate ); } /** * Print text for debug purposes * * @param string $text Text to print * * @return void */ public function debug($text) { if ($this->debug) { echo $text . "\n"; } } /** * * * @param string $length * * @return void */ public function encodeLength($length) { if ($length < 0x80) { $length = chr($length); } elseif ($length < 0x4000) { $length |= 0x8000; $length = chr(($length >> 8) & 0xFF) . chr($length & 0xFF); } elseif ($length < 0x200000) { $length |= 0xC00000; $length = chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF); } elseif ($length < 0x10000000) { $length |= 0xE0000000; $length = chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF); } elseif ($length >= 0x10000000) { $length = chr(0xF0) . chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF); } return $length; } /** * Login to RouterOS * * @param string $ip Hostname (IP or domain) of the RouterOS server * @param string $login The RouterOS username * @param string $password The RouterOS password * * @return boolean If we are connected or not */ public function connect($ip, $login, $password,$port=8728) { for ($ATTEMPT = 1; $ATTEMPT <= $this->attempts; $ATTEMPT++) { $this->connected = false; $PROTOCOL = ($this->ssl ? 'ssl://' : '' ); $context = stream_context_create(array('ssl' => array('ciphers' => 'ADH:ALL', 'verify_peer' => false, 'verify_peer_name' => false))); $this->debug('Connection attempt #' . $ATTEMPT . ' to ' . $PROTOCOL . $ip . ':' . $port . '...'); $this->socket = @stream_socket_client($PROTOCOL . $ip.':'. $port, $this->error_no, $this->error_str, $this->timeout, STREAM_CLIENT_CONNECT,$context); if ($this->socket) { socket_set_timeout($this->socket, $this->timeout); $this->write('/login', false); $this->write('=name=' . $login, false); $this->write('=password=' . $password); $RESPONSE = $this->read(false); if (isset($RESPONSE[0])) { if ($RESPONSE[0] == '!done') { if (!isset($RESPONSE[1])) { // Login method post-v6.43 $this->connected = true; break; } else { // Login method pre-v6.43 $MATCHES = array(); if (preg_match_all('/[^=]+/i', $RESPONSE[1], $MATCHES)) { if ($MATCHES[0][0] == 'ret' && strlen($MATCHES[0][1]) == 32) { $this->write('/login', false); $this->write('=name=' . $login, false); $this->write('=response=00' . md5(chr(0) . $password . pack('H*', $MATCHES[0][1]))); $RESPONSE = $this->read(false); if (isset($RESPONSE[0]) && $RESPONSE[0] == '!done') { $this->connected = true; break; } } } } } } fclose($this->socket); } sleep($this->delay); } if ($this->connected) { $this->debug('Connected...'); } else { $this->debug('Error...'); } return $this->connected; } /** * Disconnect from RouterOS * * @return void */ public function disconnect() { // let's make sure this socket is still valid. it may have been closed by something else if( is_resource($this->socket) ) { fclose($this->socket); } $this->connected = false; $this->debug('Disconnected...'); } /** * Parse response from Router OS * * @param array $response Response data * * @return array Array with parsed data */ public function parseResponse($response) { if (is_array($response)) { $PARSED = array(); $CURRENT = null; $singlevalue = null; foreach ($response as $x) { if (in_array($x, array('!fatal','!re','!trap'))) { if ($x == '!re') { $CURRENT =& $PARSED[]; } else { $CURRENT =& $PARSED[$x][]; } } elseif ($x != '!done') { $MATCHES = array(); if (preg_match_all('/[^=]+/i', $x, $MATCHES)) { if ($MATCHES[0][0] == 'ret') { $singlevalue = $MATCHES[0][1]; } $CURRENT[$MATCHES[0][0]] = (isset($MATCHES[0][1]) ? $MATCHES[0][1] : ''); } } } if (empty($PARSED) && !is_null($singlevalue)) { $PARSED = $singlevalue; } return $PARSED; } else { return array(); } } /** * Parse response from Router OS * * @param array $response Response data * * @return array Array with parsed data */ public function parseResponse4Smarty($response) { if (is_array($response)) { $PARSED = array(); $CURRENT = null; $singlevalue = null; foreach ($response as $x) { if (in_array($x, array('!fatal','!re','!trap'))) { if ($x == '!re') { $CURRENT =& $PARSED[]; } else { $CURRENT =& $PARSED[$x][]; } } elseif ($x != '!done') { $MATCHES = array(); if (preg_match_all('/[^=]+/i', $x, $MATCHES)) { if ($MATCHES[0][0] == 'ret') { $singlevalue = $MATCHES[0][1]; } $CURRENT[$MATCHES[0][0]] = (isset($MATCHES[0][1]) ? $MATCHES[0][1] : ''); } } } foreach ($PARSED as $key => $value) { $PARSED[$key] = $this->arrayChangeKeyName($value); } return $PARSED; if (empty($PARSED) && !is_null($singlevalue)) { $PARSED = $singlevalue; } } else { return array(); } } /** * Change "-" and "/" from array key to "_" * * @param array $array Input array * * @return array Array with changed key names */ public function arrayChangeKeyName(&$array) { if (is_array($array)) { foreach ($array as $k => $v) { $tmp = str_replace("-", "_", $k); $tmp = str_replace("/", "_", $tmp); if ($tmp) { $array_new[$tmp] = $v; } else { $array_new[$k] = $v; } } return $array_new; } else { return $array; } } /** * Read data from Router OS * * @param boolean $parse Parse the data? default: true * * @return array Array with parsed or unparsed data */ public function read($parse = true) { $RESPONSE = array(); $receiveddone = false; while (true) { // Read the first byte of input which gives us some or all of the length // of the remaining reply. $BYTE = ord(fread($this->socket, 1)); $LENGTH = 0; // If the first bit is set then we need to remove the first four bits, shift left 8 // and then read another byte in. // We repeat this for the second and third bits. // If the fourth bit is set, we need to remove anything left in the first byte // and then read in yet another byte. if ($BYTE & 128) { if (($BYTE & 192) == 128) { $LENGTH = (($BYTE & 63) << 8) + ord(fread($this->socket, 1)); } else { if (($BYTE & 224) == 192) { $LENGTH = (($BYTE & 31) << 8) + ord(fread($this->socket, 1)); $LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1)); } else { if (($BYTE & 240) == 224) { $LENGTH = (($BYTE & 15) << 8) + ord(fread($this->socket, 1)); $LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1)); $LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1)); } else { $LENGTH = ord(fread($this->socket, 1)); $LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1)); $LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1)); $LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1)); } } } } else { $LENGTH = $BYTE; } $_ = ""; // If we have got more characters to read, read them in. if ($LENGTH > 0) { $_ = ""; $retlen = 0; while ($retlen < $LENGTH) { $toread = $LENGTH - $retlen; $_ .= fread($this->socket, $toread); $retlen = strlen($_); } $RESPONSE[] = $_; $this->debug('>>> [' . $retlen . '/' . $LENGTH . '] bytes read.'); } // If we get a !done, make a note of it. if ($_ == "!done") { $receiveddone = true; } $STATUS = socket_get_status($this->socket); if ($LENGTH > 0) { $this->debug('>>> [' . $LENGTH . ', ' . $STATUS['unread_bytes'] . ']' . $_); } if ((!$this->connected && !$STATUS['unread_bytes']) || ($this->connected && !$STATUS['unread_bytes'] && $receiveddone)) { break; } } if ($parse) { $RESPONSE = $this->parseResponse($RESPONSE); } return $RESPONSE; } /** * Write (send) data to Router OS * * @param string $command A string with the command to send * @param mixed $param2 If we set an integer, the command will send this data as a "tag" * If we set it to boolean true, the funcion will send the comand and finish * If we set it to boolean false, the funcion will send the comand and wait for next command * Default: true * * @return boolean Return false if no command especified */ public function write($command, $param2 = true) { if ($command) { $data = explode("\n", $command); foreach ($data as $com) { $com = trim($com); fwrite($this->socket, $this->encodeLength(strlen($com)) . $com); $this->debug('<<< [' . strlen($com) . '] ' . $com); } if (gettype($param2) == 'integer') { fwrite($this->socket, $this->encodeLength(strlen('.tag=' . $param2)) . '.tag=' . $param2 . chr(0)); $this->debug('<<< [' . strlen('.tag=' . $param2) . '] .tag=' . $param2); } elseif (gettype($param2) == 'boolean') { fwrite($this->socket, ($param2 ? chr(0) : '')); } return true; } else { return false; } } /** * Write (send) data to Router OS * * @param string $com A string with the command to send * @param array $arr An array with arguments or queries * * @return array Array with parsed */ public function comm($com, $arr = array()) { $count = count($arr); $this->write($com, !$arr); $i = 0; if ($this->isIterable($arr)) { foreach ($arr as $k => $v) { switch ($k[0]) { case "?": $el = "$k=$v"; break; case "~": $el = "$k~$v"; break; default: $el = "=$k=$v"; break; } $last = ($i++ == $count - 1); $this->write($el, $last); } } return $this->read(); } /** * Standard destructor * * @return void */ public function __destruct() { $this->disconnect(); } }pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/000077500000000000000000000000001520703010000230655ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/ipfix.hpp000066400000000000000000000157031520703010000247230ustar00rootroot00000000000000// IPFIX #include "../fast_endianless.hpp" // Documentation about these fields can be found here: https://www.iana.org/assignments/ipfix/ipfix.xhtml #define IPFIX_TEMPLATE_SET_ID 2 #define IPFIX_OPTIONS_SET_ID 3 #define IPFIX_MIN_RECORD_SET_ID 256 #define IPFIX_ENTERPRISE (1 << 15) // Record types the we care about #define IPFIX_IN_BYTES 1 #define IPFIX_IN_PACKETS 2 #define IPFIX_IN_PROTOCOL 4 #define IPFIX_SRC_TOS 5 #define IPFIX_TCP_FLAGS 6 #define IPFIX_L4_SRC_PORT 7 #define IPFIX_IPV4_SRC_ADDR 8 #define IPFIX_SRC_MASK 9 #define IPFIX_INPUT_SNMP 10 #define IPFIX_L4_DST_PORT 11 #define IPFIX_IPV4_DST_ADDR 12 #define IPFIX_DST_MASK 13 #define IPFIX_OUTPUT_SNMP 14 #define IPFIX_IPV4_NEXT_HOP 15 #define IPFIX_SRC_AS 16 #define IPFIX_DST_AS 17 #define IPFIX_BGP_NEXT_HOP_IPV4_ADDRESS 18 #define IPFIX_LAST_SWITCHED 21 #define IPFIX_FIRST_SWITCHED 22 #define IPFIX_IPV6_SRC_ADDR 27 #define IPFIX_IPV6_DST_ADDR 28 #define IPFIX_IPV6_SRC_MASK 29 #define IPFIX_IPV6_DST_MASK 30 // RFC claims that this field is deprecated in favour of IPFIX_SAMPLING_PACKET_INTERVAL but many vendors use it, we need to support it too #define IPFIX_SAMPLING_INTERVAL 34 #define IPFIX_ACTIVE_TIMEOUT 36 #define IPFIX_INACTIVE_TIMEOUT 37 #define IPFIX_ENGINE_TYPE 38 #define IPFIX_ENGINE_ID 39 #define IPFIX_FRAGMENT_IDENTIFICATION 54 #define IPFIX_SOURCE_MAC_ADDRESS 56 #define IPFIX_FLOW_DIRECTION 61 #define IPFIX_IPV6_NEXT_HOP 62 #define IPFIX_DESTINATION_MAC_ADDRESS 80 // Juniper SRX uses this field to encode number of octets #define IPFIX_TOTAL_BYTES 85 // Juniper SRX uses this field to encode number of packets #define IPFIX_TOTAL_PACKETS 86 #define IPFIX_FORWARDING_STATUS 89 #define IPFIX_FLOW_END_REASON 136 // We use 8 byte encoding for "dateTimeMilliseconds" https://tools.ietf.org/html/rfc7011#page-35 #define IPFIX_FLOW_START_MILLISECONDS 152 #define IPFIX_FLOW_END_MILLISECONDS 153 // We use 8 byte encoding: https://datatracker.ietf.org/doc/html/rfc7011#section-6.1.10 #define IPFIX_FLOW_START_NANOSECONDS 156 #define IPFIX_FLOW_END_NANOSECONDS 157 // UDP ports #define IPFIX_UDP_SOURCE_PORT 180 #define IPFIX_UDP_DESTINATION_PORT 181 // TCP ports #define IPFIX_TCP_SOURCE_PORT 182 #define IPFIX_TCP_DESTINATION_PORT 183 #define IPFIX_SAMPLING_SELECTOR_ALGORITHM 304 #define IPFIX_SAMPLING_PACKET_INTERVAL 305 #define IPFIX_SAMPLING_PACKET_SPACE 306 #define IPFIX_DATALINK_FRAME_SIZE 312 #define IPFIX_DATALINK_FRAME_SECTION 315 #define IPFIX_SELECTOR_TOTAL_PACKETS_OBSERVED 318 #define IPFIX_SELECTOR_TOTAL_PACKETS_SELECTED 319 // Sampler types https://www.iana.org/assignments/psamp-parameters/psamp-parameters.xhtml #define IPFIX_SAMPLER_TYPE_SYSTEMATIC_COUNT_BASED_SAMPLING 1 class __attribute__((__packed__)) ipfix_header_common_t { public: uint16_t version = 0; // Total length of the IPFIX Message, measured in octets, including Message Header and Set(s). uint16_t length = 0; }; static_assert(sizeof(ipfix_header_common_t) == 4, "Bad size for ipfix_header_common_t"); // Described here https://datatracker.ietf.org/doc/html/rfc7011#section-3.1 class __attribute__((__packed__)) ipfix_header_t { public: uint32_t get_package_sequence_host_byte_order() const { return fast_ntoh(package_sequence); } uint32_t get_source_id_host_byte_order() const { return fast_ntoh(source_id); } uint32_t get_time_sec_host_byte_order() const { return fast_ntoh(time_sec); } uint16_t get_length_host_byte_order() const { return fast_ntoh(header.length); } private: ipfix_header_common_t header; uint32_t time_sec = 0; uint32_t package_sequence = 0; uint32_t source_id = 0; }; static_assert(sizeof(ipfix_header_t) == 16, "Bad size for ipfix_header_t"); // Set header // Described here https://datatracker.ietf.org/doc/html/rfc7011#section-3.3.2 class __attribute__((__packed__)) ipfix_set_header_common_t { public: uint16_t get_set_id_host_byte_order() const { return fast_ntoh(set_id); } uint16_t get_length_host_byte_order() const { return fast_ntoh(length); } private: uint16_t set_id = 0; uint16_t length = 0; }; static_assert(sizeof(ipfix_set_header_common_t) == 4, "Bad size for ipfix_set_header_common_t"); // Template record header https://datatracker.ietf.org/doc/html/rfc7011#section-3.4.1 class __attribute__((__packed__)) ipfix_template_record_header_t { public: uint16_t get_template_id_host_byte_order() const { return fast_ntoh(template_id); } uint16_t get_field_count_host_byte_order() const { return fast_ntoh(field_count); } private: uint16_t template_id = 0; uint16_t field_count = 0; }; static_assert(sizeof(ipfix_template_record_header_t) == 4, "Bad size for ipfix_template_record_header_t"); // Field specifier https://datatracker.ietf.org/doc/html/rfc7011#page-17 class __attribute__((__packed__)) ipfix_field_specifier_t { public: uint16_t get_type_host_byte_order() const { return fast_ntoh(type); } uint16_t get_length_host_byte_order() const { return fast_ntoh(length); } private: uint16_t type = 0; uint16_t length = 0; }; static_assert(sizeof(ipfix_field_specifier_t) == 4, "Bad size for ipfix_field_specifier_t"); // Options template record header // https://datatracker.ietf.org/doc/html/rfc7011#page-24 class __attribute__((__packed__)) ipfix_options_template_record_header_t { public: uint16_t get_template_id_host_byte_order() const { return fast_ntoh(template_id); } uint16_t get_field_count_host_byte_order() const { return fast_ntoh(field_count); } uint16_t get_scope_field_count_host_byte_order() const { return fast_ntoh(scope_field_count); } private: uint16_t template_id = 0; uint16_t field_count = 0; uint16_t scope_field_count = 0; }; static_assert(sizeof(ipfix_options_template_record_header_t) == 6, "Bad size for ipfix_options_header_t"); // It's new RFC 4 byte long format which was introduced by IPFIX update https://datatracker.ietf.org/doc/draft-ietf-opsawg-ipfix-fixes/12/ class __attribute__((__packed__)) ipfix_forwarding_status_4_bytes_t { public: // These fields carry no informatin and we have them here only to access reason_code and status uint8_t first_empty = 0; uint8_t second_empty = 0; uint8_t thrid_empty = 0; // That's only uint8_t reason_code : 6, status : 2; }; // It's not wire friendly class, feel free to add any fields class variable_length_encoding_info_t { public: // Length encoding type: one or two byte variable_length_encoding_t variable_field_length_encoding = variable_length_encoding_t::unknown; // Store variable field length uint16_t variable_field_length = 0; // Full length of variable length field (length header + payload) uint32_t record_full_length = 0; }; static_assert(sizeof(ipfix_forwarding_status_4_bytes_t) == 4, "Bad size for ipfix_forwarding_status_4_bytes_t"); pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/ipfix_collector.cpp000066400000000000000000003440661520703010000267730ustar00rootroot00000000000000#include #include #include #include "../fast_library.hpp" #include "netflow.hpp" #include "ipfix_metrics.hpp" #include "netflow_template.hpp" #include "netflow_meta_info.hpp" // We use structures defined in netflow_meta_info.hpp here #include "ipfix.hpp" #include "netflow_v9.hpp" #include "../ipfix_fields/ipfix_rfc.hpp" #include "../simple_packet_parser_ng.hpp" #include #include #include #include #include "../fastnetmon_configuration_scheme.hpp" // TODO: get rid of such tricks const template_t* peer_find_template(const std::map>& table_for_lookup, std::mutex& table_for_lookup_mutex, uint32_t source_id, uint32_t template_id, const std::string& client_addres_in_string_format); void add_update_peer_template(const netflow_protocol_version_t& netflow_version, std::map>& table_for_add, std::mutex& table_for_add_mutex, uint32_t source_id, uint32_t template_id, const std::string& client_addres_in_string_format, const template_t& field_template, bool& updated, bool& updated_existing_template); void update_device_flow_timeouts(const device_timeouts_t& device_timeouts, std::mutex& structure_mutex, std::map& timeout_storage, const std::string& client_addres_in_string_format, const netflow_protocol_version_t& netflow_protocol_version); void override_packet_fields_from_nested_packet(simple_packet_t& packet, const simple_packet_t& nested_packet); ipfix_information_database ipfix_db_instance; extern uint64_t template_netflow_ipfix_disk_writes; extern uint64_t netflow_ignored_long_flows; extern uint64_t netflow_ipfix_all_protocols_total_flows; extern uint64_t sets_per_packet_maximum_number; extern process_packet_pointer netflow_process_func_ptr; // Prototypes void save_ipfix_sampling_rates_to_disk(); // Access to inaccurate but fast time extern time_t current_inaccurate_time; extern log4cpp::Category& logger; extern fastnetmon_configuration_t fastnetmon_global_configuration; void update_ipfix_sampling_rate(uint32_t sampling_rate, const std::string& client_addres_in_string_format); std::mutex global_ipfix_templates_mutex; std::map> global_ipfix_templates; // IPFIX Sampling rates std::mutex ipfix_sampling_rates_mutex; std::map ipfix_sampling_rates; // IPFIX per device timeouts std::mutex ipfix_per_device_flow_timeouts_mutex; std::map ipfix_per_device_flow_timeouts; // TODO: get rid of it ASAP // Copy an int (possibly shorter than the target) keeping their LSBs aligned #define BE_COPY(a) memcpy((u_char*)&a + (sizeof(a) - record_length), data, record_length); // Return sampling rate for each device which sends data to us std::vector get_ipfix_sampling_rates() { std::vector system_counters; // It should be enough in common cases system_counters.reserve(15); { std::lock_guard lock(ipfix_sampling_rates_mutex); // Copy all elements to output for (auto& elem : ipfix_sampling_rates) { system_counters.push_back(system_counter_t(elem.first, (uint64_t)elem.second, metric_type_t::gauge, "Sampling rate")); } } return system_counters; } std::vector get_ipfix_stats() { std::vector system_counter; system_counter.push_back(system_counter_t("ipfix_total_flows", ipfix_total_flows, metric_type_t::counter, ipfix_total_flows_desc)); system_counter.push_back( system_counter_t("ipfix_total_packets", ipfix_total_packets, metric_type_t::counter, ipfix_total_packets_desc)); system_counter.push_back(system_counter_t("ipfix_total_ipv4_flows", ipfix_total_ipv4_flows, metric_type_t::counter, ipfix_total_ipv4_flows_desc)); system_counter.push_back(system_counter_t("ipfix_total_ipv6_flows", ipfix_total_ipv6_flows, metric_type_t::counter, ipfix_total_ipv6_flows_desc)); system_counter.push_back(system_counter_t("ipfix_duration_0_seconds", ipfix_duration_0_seconds, metric_type_t::counter, ipfix_duration_0_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_1_seconds", ipfix_duration_less_1_seconds, metric_type_t::counter, ipfix_duration_less_1_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_2_seconds", ipfix_duration_less_2_seconds, metric_type_t::counter, ipfix_duration_less_2_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_3_seconds", ipfix_duration_less_3_seconds, metric_type_t::counter, ipfix_duration_less_3_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_5_seconds", ipfix_duration_less_5_seconds, metric_type_t::counter, ipfix_duration_less_5_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_10_seconds", ipfix_duration_less_10_seconds, metric_type_t::counter, ipfix_duration_less_10_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_15_seconds", ipfix_duration_less_15_seconds, metric_type_t::counter, ipfix_duration_less_15_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_30_seconds", ipfix_duration_less_30_seconds, metric_type_t::counter, ipfix_duration_less_30_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_60_seconds", ipfix_duration_less_60_seconds, metric_type_t::counter, ipfix_duration_less_60_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_90_seconds", ipfix_duration_less_90_seconds, metric_type_t::counter, ipfix_duration_less_90_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_less_180_seconds", ipfix_duration_less_180_seconds, metric_type_t::counter, ipfix_duration_less_180_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_exceed_180_seconds", ipfix_duration_exceed_180_seconds, metric_type_t::counter, ipfix_duration_exceed_180_seconds_desc)); system_counter.push_back(system_counter_t("ipfix_duration_negative", ipfix_duration_negative, metric_type_t::counter, ipfix_duration_negative_desc)); system_counter.push_back(system_counter_t("ipfix_flows_end_reason_idle_timeout", ipfix_flows_end_reason_idle_timeout, metric_type_t::counter, ipfix_flows_end_reason_idle_timeout_desc)); system_counter.push_back(system_counter_t("ipfix_flows_end_reason_active_timeout", ipfix_flows_end_reason_active_timeout, metric_type_t::counter, ipfix_flows_end_reason_active_timeout_desc)); system_counter.push_back(system_counter_t("ipfix_flows_end_reason_end_of_flow_timeout", ipfix_flows_end_reason_end_of_flow_timeout, metric_type_t::counter, ipfix_flows_end_reason_end_of_flow_timeout_desc)); system_counter.push_back(system_counter_t("ipfix_flows_end_reason_force_end_timeout", ipfix_flows_end_reason_force_end_timeout, metric_type_t::counter, ipfix_flows_end_reason_force_end_timeout_desc)); system_counter.push_back(system_counter_t("ipfix_flows_end_reason_lack_of_resource_timeout", ipfix_flows_end_reason_lack_of_resource_timeout, metric_type_t::counter, ipfix_flows_end_reason_lack_of_resource_timeout_desc)); system_counter.push_back(system_counter_t("ipfix_data_packet_number", ipfix_data_packet_number, metric_type_t::counter, ipfix_data_packet_number_desc)); system_counter.push_back(system_counter_t("ipfix_data_templates_number", ipfix_data_templates_number, metric_type_t::counter, ipfix_data_templates_number_desc)); system_counter.push_back(system_counter_t("ipfix_options_templates_number", ipfix_options_templates_number, metric_type_t::counter, ipfix_options_templates_number_desc)); system_counter.push_back(system_counter_t("ipfix_options_packet_number", ipfix_options_packet_number, metric_type_t::counter, ipfix_options_packet_number_desc)); system_counter.push_back(system_counter_t("ipfix_packets_with_unknown_templates", ipfix_packets_with_unknown_templates, metric_type_t::counter, ipfix_packets_with_unknown_templates_desc)); system_counter.push_back(system_counter_t("ipfix_custom_sampling_rate_received", ipfix_custom_sampling_rate_received, metric_type_t::counter, ipfix_custom_sampling_rate_received_desc)); system_counter.push_back(system_counter_t("ipfix_sampling_rate_changes", ipfix_sampling_rate_changes, metric_type_t::counter, ipfix_sampling_rate_changes_desc)); system_counter.push_back(system_counter_t("ipfix_marked_zero_next_hop_and_zero_output_as_dropped", ipfix_marked_zero_next_hop_and_zero_output_as_dropped, metric_type_t::counter, ipfix_marked_zero_next_hop_and_zero_output_as_dropped_desc)); system_counter.push_back(system_counter_t("ipfix_template_updates_number_due_to_real_changes", ipfix_template_data_updates, metric_type_t::counter, ipfix_template_data_updates_desc)); system_counter.push_back(system_counter_t("ipfix_packets_with_padding", ipfix_packets_with_padding, metric_type_t::counter, ipfix_packets_with_padding_desc)); system_counter.push_back(system_counter_t("ipfix_inline_headers", ipfix_inline_headers, metric_type_t::counter, ipfix_inline_headers_desc)); system_counter.push_back(system_counter_t("ipfix_protocol_version_adjustments", ipfix_protocol_version_adjustments, metric_type_t::counter, ipfix_protocol_version_adjustments_desc)); system_counter.push_back(system_counter_t("ipfix_too_large_field", ipfix_too_large_field, metric_type_t::counter, ipfix_too_large_field_desc)); system_counter.push_back(system_counter_t("ipfix_forwarding_status", ipfix_forwarding_status, metric_type_t::counter, ipfix_forwarding_status_desc)); system_counter.push_back(system_counter_t("ipfix_inline_header_parser_error", ipfix_inline_header_parser_error, metric_type_t::counter, ipfix_inline_header_parser_error_desc)); system_counter.push_back(system_counter_t("ipfix_inline_encoding_error", ipfix_inline_encoding_error, metric_type_t::counter, ipfix_inline_encoding_error_desc)); system_counter.push_back(system_counter_t("ipfix_inline_header_parser_success", ipfix_inline_header_parser_success, metric_type_t::counter, ipfix_inline_header_parser_success_desc)); system_counter.push_back(system_counter_t("ipfix_active_flow_timeout_received", ipfix_active_flow_timeout_received, metric_type_t::counter, ipfix_active_flow_timeout_received_desc)); system_counter.push_back(system_counter_t("ipfix_inactive_flow_timeout_received", ipfix_inactive_flow_timeout_received, metric_type_t::counter, ipfix_inactive_flow_timeout_received_desc)); system_counter.push_back(system_counter_t("ipfix_sets_with_anomaly_padding", ipfix_sets_with_anomaly_padding, metric_type_t::counter, ipfix_sets_with_anomaly_padding_desc)); return system_counter; } // Checks that all bytes in range are zero bool are_all_padding_bytes_are_zero(const uint8_t* padding_start_in_packet, int64_t padding_length) { logger << log4cpp::Priority::DEBUG << "Detected " << padding_length << " byte long padding"; for (int padding_byte_index = 0; padding_byte_index < padding_length; padding_byte_index++) { const uint8_t* padding_byte_ptr = (const uint8_t*)(padding_start_in_packet + padding_byte_index); logger << log4cpp::Priority::DEBUG << padding_byte_index << "nd padding byte value " << uint32_t(*padding_byte_ptr); if (*padding_byte_ptr != 0) { return false; } } // All bytes in range are zero return true; } bool read_ipfix_options_template(const uint8_t* packet, uint32_t offset, uint32_t set_length, bool& template_cache_update_required, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t& template_total_read_length); // Read options template set // https://tools.ietf.org/html/rfc5101#page-18 bool process_ipfix_options_template_set(const uint8_t* packet, uint32_t set_length, uint32_t source_id, const std::string& client_addres_in_string_format) { // Ensure that we have enough data to read set header if (set_length < sizeof(ipfix_set_header_common_t)) { logger << log4cpp::Priority::ERROR << "Short IPFIX options template header " << set_length << " bytes. " << "Agent IP: " << client_addres_in_string_format; return false; } // Read generic set header const ipfix_set_header_common_t* options_set_header = (ipfix_set_header_common_t*)packet; uint16_t set_id = options_set_header->get_set_id_host_byte_order(); // Yes, we have flow set length in options_template_header->length but we've read it on previous step and we can use it from argument of this function instead // Ensure that we're dealing with options set if (set_id != IPFIX_OPTIONS_SET_ID) { logger << log4cpp::Priority::ERROR << "For options template we expect " << IPFIX_OPTIONS_SET_ID << "set_id but got " "another id: " << set_id << "Agent IP: " << client_addres_in_string_format; return false; } // Shift pointer to length of set header uint32_t offset = sizeof(ipfix_set_header_common_t); bool template_cache_update_required = false; // That's time to read all available templates in set for (; offset < set_length;) { uint32_t options_template_total_read_length = 0; bool read_options_template_res = read_ipfix_options_template(packet, offset, set_length, template_cache_update_required, source_id, client_addres_in_string_format, options_template_total_read_length); if (!read_options_template_res) { return false; } logger << log4cpp::Priority::DEBUG << "Correcly read " << options_template_total_read_length << " bytes long options template"; // Move forward on length of read section offset += options_template_total_read_length; // Time to process padding, we may have some zero bytes here and we need to ensure that they're zeroes as RFC requires // https://datatracker.ietf.org/doc/html/rfc7011#page-18 // In case of options template padding may be 1, 2, 3, 4, 5 bytes only due to length of ipfix_options_template_record_header_t (6 bytes) // and if we have 1..5 bytes on end of packet and all of them are zero then we can stop this loop without triggering error // Use larger signed type to be sure about careful subtraction int64_t padding_length = set_length - offset; const uint8_t* padding_start_in_packet = (const uint8_t*)(packet + offset); if (padding_length >= 1 && padding_length <= 5) { logger << log4cpp::Priority::DEBUG << "Detected " << padding_length << " byte long padding"; bool all_padding_bytes_are_zero = are_all_padding_bytes_are_zero(padding_start_in_packet, padding_length); if (all_padding_bytes_are_zero) { logger << log4cpp::Priority::DEBUG << "All padding bytes are zero, feel free to correctly stop processing"; // Stop loop correctly without triggering error break; } else { logger << log4cpp::Priority::ERROR << "Non zero padding bytes, semething is wrong with packet"; // We have to report error return false; } } } return true; } // Reads single IPFIX options template // This function designed same way as read_ipfix_data_template // please keep it this way for clarity reasons as both of them are too complex on it's own and we need to // use simpilar design to simplify them // In case of successful read it will set amount of read data in template_total_read_length bool read_ipfix_options_template(const uint8_t* packet, uint32_t template_records_start_offset, uint32_t set_length, bool& template_cache_update_required, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t& template_total_read_length) { // Start from offset where we expect template record header uint32_t offset = template_records_start_offset; // logger << log4cpp::Priority::INFO << "set_id " << set_id << " set_length: " << set_length; // Check that we have enough space in packet to read ipfix_options_template_record_header_t if (offset + sizeof(ipfix_options_template_record_header_t) > set_length) { logger << log4cpp::Priority::ERROR << "Could not read options templete header for IPFIX options template. " << "Agent IP: " << client_addres_in_string_format << " offset: " << offset << " set_length: " << set_length; return false; } const ipfix_options_template_record_header_t* ipfix_options_template_record_header = (const ipfix_options_template_record_header_t*)(packet + offset); // logger << log4cpp::Priority::INFO << "raw undecoded data template_id: " << options_nested_header->template_id << // " field_count: " << options_nested_header->field_count // << " scope_field_count: " << options_nested_header->scope_field_count; // Get all fields from options_nested_header uint16_t template_id = ipfix_options_template_record_header->get_template_id_host_byte_order(); uint16_t field_count = ipfix_options_template_record_header->get_field_count_host_byte_order(); uint16_t scope_field_count = ipfix_options_template_record_header->get_scope_field_count_host_byte_order(); // According to RFC scope_field_count must not be zero but I'll assume that some vendors may fail to implement it // https://tools.ietf.org/html/rfc7011#page-24 // logger << log4cpp::Priority::INFO << "Options template id: " << template_id << " field_count: " << field_count // << " scope_field_count: " << scope_field_count; if (template_id <= 255) { logger << log4cpp::Priority::ERROR << "Template ID for IPFIX options template should be bigger than 255, got " << template_id << " Agent IP: " << client_addres_in_string_format; return false; } logger << log4cpp::Priority::DEBUG << "Options template id: " << template_id << " field_count: " << field_count << " scope_field_count: " << scope_field_count; // According to RFC field_count includes scope_field_count // https://tools.ietf.org/html/rfc7011#page-24 "Number of all fields in this Options Template Record, including the Scope Fields." if (scope_field_count > field_count) { logger << log4cpp::Priority::ERROR << "Number of scope fields " << scope_field_count << " cannot exceed number of all fields: " << field_count << " Agent IP: " << client_addres_in_string_format; return false; } // Calculate number of all normal fields uint16_t normal_field_count = field_count - scope_field_count; // Shift pointer on length of header offset += sizeof(ipfix_options_template_record_header_t); uint32_t scopes_total_size = 0; uint32_t scopes_payload_total_size = 0; // Then we have scope fields in packet, I'm not going to process them, I'll just skip them for (int scope_index = 0; scope_index < scope_field_count; scope_index++) { // Check that our attempt to read ipfix_field_specifier_t will not exceed packet length if (offset + sizeof(ipfix_field_specifier_t) > set_length) { logger << log4cpp::Priority::ERROR << "Attempt to read IPFIX set_record outside of packet. " << "Agent IP: " << client_addres_in_string_format; return false; } const ipfix_field_specifier_t* current_scopes_record = (const ipfix_field_specifier_t*)(packet + offset); uint16_t scope_field_size = current_scopes_record->get_length_host_byte_order(); uint16_t scope_field_type = current_scopes_record->get_type_host_byte_order(); logger << log4cpp::Priority::DEBUG << "Reading scope section with size " << scope_field_size << " and type: " << scope_field_type; // Increment scopes size scopes_total_size += sizeof(ipfix_field_specifier_t); // Increment payload size scopes_payload_total_size += scope_field_size; // Shift pointer to the end of current scope field offset += sizeof(ipfix_field_specifier_t); } // We've reached normal fields section uint32_t normal_fields_total_size = 0; std::vector template_records_map; uint32_t normal_fields_payload_total_size = 0; // These fields use quite complicated encoding and we need to identify them first bool ipfix_variable_length_elements_used = false; // Try to read all normal fields for (int field_index = 0; field_index < normal_field_count; field_index++) { // Check that our attempt to read ipfix_field_specifier_t will not exceed packet length if (offset + sizeof(ipfix_field_specifier_t) > set_length) { logger << log4cpp::Priority::ERROR << "Attempt to read IPFIX set_record outside of packet for normal field. " << "Agent IP: " << client_addres_in_string_format; return false; } const ipfix_field_specifier_t* current_normal_record = (const ipfix_field_specifier_t*)(packet + offset); template_record_t current_record{}; current_record.record_type = current_normal_record->get_type_host_byte_order(); current_record.record_length = current_normal_record->get_length_host_byte_order(); // it's special size which actually means that variable length encoding was used for this field // https://datatracker.ietf.org/doc/html/rfc7011#page-37 if (current_record.record_length == 65535) { ipfix_variable_length_elements_used = true; } logger << log4cpp::Priority::DEBUG << "Reading IPFIX options field with type " << current_record.record_type << " and length: " << current_record.record_length << " enterprise flag " << current_record.enterprise_bit << " enterprise number: " << current_record.enterprise_number; // Increment total field size normal_fields_total_size += sizeof(ipfix_field_specifier_t); // Increment total payload size normal_fields_payload_total_size += current_record.record_length; // Shift pointer to the end of current normal field offset += sizeof(ipfix_field_specifier_t); // If we have Enterprise flag then it means that we have 4 byte long Enterprise Number next after and we need to // skip it https://datatracker.ietf.org/doc/html/rfc7011#page-17 if (current_record.record_type & IPFIX_ENTERPRISE) { current_record.enterprise_bit = true; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::INFO << "Enterprise field detected for field specified with type " << current_record.record_type; } // Ensure that we can read Enterprise Number if (offset + sizeof(uint32_t) > set_length) { logger << log4cpp::Priority::ERROR << "IPFIX template set is too short " << set_length << " to read enterprise number. Agent IP: " << client_addres_in_string_format << " offset: " << offset; return false; } // Read enterprise number current_record.enterprise_number = fast_ntoh(*(uint32_t*)(packet + offset)); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::INFO << "Enterprise field detected for field specified with type " << current_record.record_type << " and enterprise number is " << current_record.enterprise_number; } // Jump one byte forward offset += sizeof(uint32_t); } template_records_map.push_back(current_record); } template_t field_template{}; field_template.template_id = template_id; field_template.records = template_records_map; // I do not think that we use it in our logic but I think it's reasonable to set it to number of normal fields field_template.num_records = normal_field_count; field_template.total_length = normal_fields_payload_total_size + scopes_payload_total_size; field_template.type = netflow_template_type_t::Options; field_template.ipfix_variable_length_elements_used = ipfix_variable_length_elements_used; field_template.option_scope_length = scopes_payload_total_size; // We need to know when we received it field_template.timestamp = current_inaccurate_time; // logger << log4cpp::Priority::INFO << "Read options template:" << print_template(field_template); // Add/update template bool updated = false; bool updated_existing_template = false; add_update_peer_template(netflow_protocol_version_t::ipfix, global_ipfix_templates, global_ipfix_templates_mutex, source_id, template_id, client_addres_in_string_format, field_template, updated, updated_existing_template); // This code is not perfect from locks perspective as we read global_ipfix_templates without any locks below // NB! Please be careful with changing name of variable as it's part of serialisation protocol if (updated_existing_template) { ipfix_template_data_updates++; } // If we have any changes for this template, let's flush them to disk if (updated) { template_cache_update_required = true; } // Calculate amount of data we read in this function template_total_read_length = offset - template_records_start_offset; return true; } bool read_ipfix_data_template(const uint8_t* packet, uint32_t offset, uint32_t set_length, bool& template_cache_update_required, uint32_t template_sequence_number, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t& template_total_read_length); // Process IPFIX data template set bool process_ipfix_data_template_set(const uint8_t* packet, uint32_t set_length, uint32_t source_id, const std::string& client_addres_in_string_format) { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Starting process_ipfix_data_template_set for set_length " << set_length; } // Ensure that we have enough data to read set header if (set_length < sizeof(ipfix_set_header_common_t)) { logger << log4cpp::Priority::ERROR << "Short IPFIX set template header " << set_length << " bytes. Agent IP: " << client_addres_in_string_format; return false; } const ipfix_set_header_common_t* template_set_header = (const ipfix_set_header_common_t*)packet; // Additional sanity check that set_id is for data template if (template_set_header->get_set_id_host_byte_order() != IPFIX_TEMPLATE_SET_ID) { logger << log4cpp::Priority::ERROR << "Function process_ipfix_data_template_set expects only " "IPFIX_TEMPLATE_SET_ID but " "got another id: " << template_set_header->get_set_id_host_byte_order() << " Agent IP: " << client_addres_in_string_format; return false; } bool template_cache_update_required = false; // To count number of templates we read uint32_t template_sequence_number = 0; // Shift pointer to length of set header uint32_t offset = sizeof(ipfix_set_header_common_t); // That's time to read all available templates in set for (; offset < set_length;) { template_sequence_number++; uint32_t template_total_read_length = 0; bool read_remplate_result = read_ipfix_data_template(packet, offset, set_length, template_cache_update_required, template_sequence_number, source_id, client_addres_in_string_format, template_total_read_length); if (!read_remplate_result) { logger << log4cpp::Priority::ERROR << "Cannot read template correctly"; return false; } // Move forward on length of read section offset += template_total_read_length; } return true; } // In case of successful read of template function will set amount of read data in template_total_read_length bool read_ipfix_data_template(const uint8_t* packet, uint32_t template_records_start_offset, uint32_t set_length, bool& template_cache_update_required, uint32_t template_sequence_number, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t& template_total_read_length) { // Start from offset where we expect template record header uint32_t offset = template_records_start_offset; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Starting read_ipfix_data_template for set_length " << set_length; } if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Reading template sequence number " << template_sequence_number; } // We need to ensure that we have enough space for reading template record header if (offset + sizeof(ipfix_template_record_header_t) > set_length) { logger << log4cpp::Priority::ERROR << "Set is too short to read IPFIX template header with length " << sizeof(ipfix_template_record_header_t) << " bytes. Offset: " << offset; return false; } const ipfix_template_record_header_t* template_record_header = (const ipfix_template_record_header_t*)(packet + offset); uint32_t template_id = template_record_header->get_template_id_host_byte_order(); uint32_t record_count = template_record_header->get_field_count_host_byte_order(); // Shift pointer on length of header offset += sizeof(ipfix_template_record_header_t); std::vector template_records_map; uint32_t total_template_data_size = 0; // These fields use quite complicated encoding and we need to identify them first bool ipfix_variable_length_elements_used = false; // Read all field specifiers for (uint32_t i = 0; i < record_count; i++) { // Ensure that we have enough space to read field specifier structure if (offset + sizeof(ipfix_field_specifier_t) > set_length) { logger << log4cpp::Priority::ERROR << "Short IPFIX set template. Agent IP: " << client_addres_in_string_format; return false; } const ipfix_field_specifier_t* field_specifier = (const ipfix_field_specifier_t*)(packet + offset); uint32_t record_type = field_specifier->get_type_host_byte_order(); uint32_t record_length = field_specifier->get_length_host_byte_order(); template_record_t current_record; current_record.record_type = record_type; current_record.record_length = record_length; // it's special size which actually means that variable length encoding was used for this field // https://datatracker.ietf.org/doc/html/rfc7011#page-37 if (record_length == 65535) { ipfix_variable_length_elements_used = true; } // Move next on length of template record offset += sizeof(ipfix_field_specifier_t); // If we have Enterprise flag then it means that we have 4 byte long Enterprise Number next after and we need to // skip it https://datatracker.ietf.org/doc/html/rfc7011#page-17 if (record_type & IPFIX_ENTERPRISE) { current_record.enterprise_bit = true; // Ensure that we can read Enterprise Number if (offset + sizeof(uint32_t) > set_length) { logger << log4cpp::Priority::ERROR << "IPFIX template set is too short " << set_length << " to read enterprise number. Agent IP: " << client_addres_in_string_format << " offset: " << offset; return false; } // Read enterprise number current_record.enterprise_number = fast_ntoh(*(uint32_t*)(packet + offset)); offset += sizeof(uint32_t); } template_records_map.push_back(current_record); // We increment template data size only when we have only fixed fields // We ensure that we never use this value in case when variable field encoding is used if (!ipfix_variable_length_elements_used) { total_template_data_size += record_length; } } // We use same struct as Netflow v9 because Netflow v9 and IPFIX use similar fields template_t field_template; field_template.template_id = template_id; field_template.num_records = record_count; field_template.total_length = total_template_data_size; field_template.records = template_records_map; field_template.type = netflow_template_type_t::Data; field_template.ipfix_variable_length_elements_used = ipfix_variable_length_elements_used; // We need to know when we received it field_template.timestamp = current_inaccurate_time; bool updated = false; bool updated_existing_template = false; add_update_peer_template(netflow_protocol_version_t::ipfix, global_ipfix_templates, global_ipfix_templates_mutex, source_id, template_id, client_addres_in_string_format, field_template, updated, updated_existing_template); // If we have any changes for this template, let's flush them to disk if (updated) { template_cache_update_required = true; } if (updated_existing_template) { ipfix_template_data_updates++; } // Calculate amount of data we read in this function template_total_read_length = offset - template_records_start_offset; return true; } bool ipfix_record_to_flow(uint32_t record_type, uint32_t record_length, const uint8_t* data, simple_packet_t& packet, netflow_meta_info_t& flow_meta) { switch (record_type) { case IPFIX_IN_BYTES: if (record_length > sizeof(packet.length)) { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_IN_BYTES: " << record_length; } } else { BE_COPY(packet.length); // decode data in network byte order to host byte order packet.length = fast_ntoh(packet.length); // IPFIX carries only information about number of octets including IP headers and IP payload // which is exactly what we need for ip_length field packet.ip_length = packet.length; } break; case IPFIX_IN_PACKETS: if (record_length > sizeof(packet.number_of_packets)) { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_IN_PACKETS: " << record_length; } } else { BE_COPY(packet.number_of_packets); packet.number_of_packets = fast_ntoh(packet.number_of_packets); } break; case IPFIX_TOTAL_PACKETS: if (record_length == 8) { uint64_t packets_number = 0; memcpy(&packets_number, data, record_length); packet.number_of_packets = fast_ntoh(packets_number); } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_TOTAL_PACKETS: " << record_length; } } break; case IPFIX_TOTAL_BYTES: if (record_length == 8) { uint64_t bytes_number = 0; memcpy(&bytes_number, data, record_length); packet.length = fast_ntoh(bytes_number); // IPFIX carries only information about number of octets including IP headers and IP payload // which is exactly what we need for ip_length field packet.ip_length = packet.length; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_TOTAL_BYTES: " << record_length; } } break; case IPFIX_IN_PROTOCOL: if (record_length > sizeof(packet.protocol)) { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_IN_PROTOCOL: " << record_length; } } else { BE_COPY(packet.protocol); packet.protocol = fast_ntoh(packet.protocol); } break; case IPFIX_TCP_FLAGS: if (record_length == 1) { BE_COPY(packet.flags); } else if (record_length == 2) { // If exported as a single octet with reduced-size encoding, this Information Element covers the low-order // octet of this field (i.e, bits 0x80 to 0x01), omitting the ECN Nonce Sum and the three Future Use bits. // https://www.iana.org/assignments/ipfix/ipfix.xhtml // So we just copy second byte which carries same information as when it encoded with 1 byte memcpy(&packet.flags, data + 1, 1); } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_TCP_FLAGS: " << record_length; } } break; case IPFIX_L4_SRC_PORT: if (record_length > sizeof(packet.source_port)) { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_L4_SRC_PORT: " << record_length; } } else { BE_COPY(packet.source_port); // We should convert port to host byte order packet.source_port = fast_ntoh(packet.source_port); } break; case IPFIX_L4_DST_PORT: if (record_length > sizeof(packet.destination_port)) { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_L4_DST_PORT: " << record_length; } } else { BE_COPY(packet.destination_port); // We should convert port to host byte order packet.destination_port = fast_ntoh(packet.destination_port); } break; case IPFIX_TCP_SOURCE_PORT: // This is unusual encoding used only by AMD Pensando // We enable it only we know that packet is TCP if (packet.protocol == IPPROTO_TCP) { if (record_length == 2) { uint16_t port = 0; memcpy(&port, data, record_length); packet.source_port = fast_ntoh(port); } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_TCP_SOURCE_PORT: " << record_length; } } } break; case IPFIX_TCP_DESTINATION_PORT: // This is unusual encoding used only by AMD Pensando // We enable it only we know that packet is TCP if (packet.protocol == IPPROTO_TCP) { if (record_length == 2) { uint16_t port = 0; memcpy(&port, data, record_length); packet.destination_port = fast_ntoh(port); } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_TCP_DESTINATION_PORT: " << record_length; } } } break; case IPFIX_UDP_SOURCE_PORT: // This is unusual encoding used only by AMD Pensando // We enable it only we know that packet is UDP if (packet.protocol == IPPROTO_UDP) { if (record_length == 2) { uint16_t port = 0; memcpy(&port, data, record_length); packet.source_port = fast_ntoh(port); } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_UDP_SOURCE_PORT: " << record_length; } } } break; case IPFIX_UDP_DESTINATION_PORT: // This is unusual encoding used only by AMD Pensando // We enable it only we know that packet is UDP if (packet.protocol == IPPROTO_UDP) { if (record_length == 2) { uint16_t port = 0; memcpy(&port, data, record_length); packet.destination_port = fast_ntoh(port); } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_UDP_DESTINATION_PORT: " << record_length; } } } break; case IPFIX_IPV4_SRC_ADDR: if (record_length > sizeof(packet.src_ip)) { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_IPV4_SRC_ADDR: " << record_length; } } else { memcpy(&packet.src_ip, data, record_length); } break; case IPFIX_IPV4_DST_ADDR: if (record_length > sizeof(packet.dst_ip)) { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_IPV4_DST_ADDR: " << record_length; } } else { memcpy(&packet.dst_ip, data, record_length); } break; // There is a similar field IPFIX_BGP_NEXT_HOP_IPV4_ADDRESS but with slightly different meaning case IPFIX_IPV4_NEXT_HOP: if (record_length == 4) { uint32_t ip_next_hop_ipv4 = 0; memcpy(&ip_next_hop_ipv4, data, record_length); flow_meta.ip_next_hop_ipv4_set = true; flow_meta.ip_next_hop_ipv4 = ip_next_hop_ipv4; // std::cout << "IP next hop: " << convert_ip_as_uint_to_string(ip_next_hop_ipv4) << std::endl; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_IPV4_NEXT_HOP: " << record_length; } } break; // There is a similar field IPFIX_IPV4_NEXT_HOP but with slightly different meaning case IPFIX_BGP_NEXT_HOP_IPV4_ADDRESS: // Juniper MX uses this field if (record_length == 4) { uint32_t bgp_next_hop_ipv4 = 0; memcpy(&bgp_next_hop_ipv4, data, record_length); flow_meta.bgp_next_hop_ipv4_set = true; flow_meta.bgp_next_hop_ipv4 = bgp_next_hop_ipv4; // std::cout << "BGP next hop: " << convert_ip_as_uint_to_string(bgp_next_hop_ipv4) << std::endl; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_BGP_NEXT_HOP_IPV4_ADDRESS: " << record_length; } } break; case IPFIX_IPV6_NEXT_HOP: // Juniper MX uses this field if (record_length == 16) { in6_addr bgp_next_hop_ipv6{}; memcpy(&bgp_next_hop_ipv6, data, record_length); flow_meta.bgp_next_hop_ipv6_set = true; flow_meta.bgp_next_hop_ipv6 = bgp_next_hop_ipv6; // std::cout << "bgp next hop: " << print_ipv6_address(ipv6_next_hop) << std::endl; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_IPV6_NEXT_HOP: " << record_length; } } break; // According to https://www.iana.org/assignments/ipfix/ipfix.xhtml ASN can be 4 byte only // Unfortunately, customer (Intermedia) shared pcap with ASNs encoded as 2 byte values :( case IPFIX_SRC_AS: if (record_length == 4) { uint32_t src_asn = 0; memcpy(&src_asn, data, record_length); src_asn = fast_ntoh(src_asn); packet.src_asn = src_asn; } else if (record_length == 2) { uint16_t src_asn = 0; memcpy(&src_asn, data, record_length); src_asn = fast_ntoh(src_asn); packet.src_asn = src_asn; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_SRC_AS: " << record_length; } } break; case IPFIX_DST_AS: if (record_length == 4) { uint32_t dst_asn = 0; memcpy(&dst_asn, data, record_length); dst_asn = fast_ntoh(dst_asn); packet.dst_asn = dst_asn; } else if (record_length == 2) { uint16_t dst_asn = 0; memcpy(&dst_asn, data, record_length); dst_asn = fast_ntoh(dst_asn); packet.dst_asn = dst_asn; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_DST_AS: " << record_length; } } break; case IPFIX_SOURCE_MAC_ADDRESS: if (record_length == 6) { // Copy it directly to packet structure memcpy(&packet.source_mac, data, record_length); } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for IPFIX_SOURCE_MAC_ADDRESS"; } } break; case IPFIX_DESTINATION_MAC_ADDRESS: if (record_length == 6) { // Copy it directly to packet structure memcpy(&packet.destination_mac, data, record_length); } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for IPFIX_DESTINATION_MAC_ADDRESS"; } } break; // According to https://www.iana.org/assignments/ipfix/ipfix.xhtml interfaces can be 4 byte only case IPFIX_INPUT_SNMP: if (record_length == 4) { uint32_t input_interface = 0; memcpy(&input_interface, data, record_length); input_interface = fast_ntoh(input_interface); packet.input_interface = input_interface; } else if (record_length == 2) { uint16_t input_interface = 0; memcpy(&input_interface, data, record_length); input_interface = fast_ntoh(input_interface); packet.input_interface = input_interface; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_INPUT_SNMP: " << record_length; } } break; case IPFIX_OUTPUT_SNMP: if (record_length == 4) { uint32_t output_interface = 0; memcpy(&output_interface, data, record_length); output_interface = fast_ntoh(output_interface); packet.output_interface = output_interface; } else if (record_length == 2) { uint16_t output_interface = 0; memcpy(&output_interface, data, record_length); output_interface = fast_ntoh(output_interface); packet.output_interface = output_interface; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_OUTPUT_SNMP: " << record_length; } } break; case IPFIX_IPV6_SRC_ADDR: // It should be 16 bytes only if (true) { if (record_length == 16) { memcpy(&packet.src_ipv6, data, record_length); // Set protocol version to IPv6 packet.ip_protocol_version = 6; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_IPV6_SRC_ADDR: " << record_length; } } } break; case IPFIX_IPV6_DST_ADDR: // It should be 16 bytes only if (true) { if (record_length == 16) { memcpy(&packet.dst_ipv6, data, record_length); // Set protocol version to IPv6 packet.ip_protocol_version = 6; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_IPV6_DST_ADDR: " << record_length; } } } break; case IPFIX_FIRST_SWITCHED: // Mikrotik uses this encoding if (record_length == 4) { uint32_t flow_started = 0; memcpy(&flow_started, data, record_length); flow_started = fast_ntoh(flow_started); packet.flow_start = flow_started; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_FIRST_SWITCHED: " << record_length; } } break; case IPFIX_LAST_SWITCHED: // Mikrotik uses this encoding if (record_length == 4) { uint32_t flow_finished = 0; memcpy(&flow_finished, data, record_length); flow_finished = fast_ntoh(flow_finished); packet.flow_end = flow_finished; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_LAST_SWITCHED: " << record_length; } } break; // Juniper uses IPFIX_FLOW_START_MILLISECONDS and IPFIX_FLOW_END_MILLISECONDS case IPFIX_FLOW_START_MILLISECONDS: if (record_length == 8) { uint64_t flow_started = 0; memcpy(&flow_started, data, record_length); flow_started = fast_ntoh(flow_started); // We cast unsigned to signed and it may cause issues packet.flow_start = flow_started; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_FLOW_START_MILLISECONDS: " << record_length; } } break; case IPFIX_FLOW_END_MILLISECONDS: if (record_length == 8) { uint64_t flow_finished = 0; memcpy(&flow_finished, data, record_length); flow_finished = fast_ntoh(flow_finished); // We cast unsigned to signed and it may cause issues packet.flow_end = flow_finished; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_FLOW_END_MILLISECONDS: " << record_length; } } break; // Netgate TNSR uses IPFIX_FLOW_START_NANOSECONDS and IPFIX_FLOW_END_NANOSECONDS case IPFIX_FLOW_START_NANOSECONDS: if (record_length == 8) { uint64_t flow_started = 0; memcpy(&flow_started, data, record_length); flow_started = fast_ntoh(flow_started); // We cast unsigned to signed and it may cause issues packet.flow_start = flow_started; // Convert to milliseconds packet.flow_start = packet.flow_start / 1000000; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_FLOW_START_NANOSECONDS: " << record_length; } } break; case IPFIX_FLOW_END_NANOSECONDS: if (record_length == 8) { uint64_t flow_finished = 0; memcpy(&flow_finished, data, record_length); flow_finished = fast_ntoh(flow_finished); // We cast unsigned to signed and it may cause issues packet.flow_end = flow_finished; // Convert to milliseconds packet.flow_end = packet.flow_end / 1000000; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_FLOW_END_NANOSECONDS: " << record_length; } } break; case IPFIX_FORWARDING_STATUS: // TODO: we did using theoretical information and did not test it at all // Documented here: https://www.iana.org/assignments/ipfix/ipfix.xhtml#forwarding-status // Forwarding status is encoded on 1 byte with the 2 left bits giving the status and the 6 remaining bits giving the reason code. if (record_length == 1) { uint8_t forwarding_status = 0; memcpy(&forwarding_status, data, record_length); const netflow9_forwarding_status_t* forwarding_status_structure = (const netflow9_forwarding_status_t*)&forwarding_status; // Decode numbers into forwarding statuses packet.forwarding_status = forwarding_status_from_integer(forwarding_status_structure->status); flow_meta.received_forwarding_status = true; ipfix_forwarding_status++; // logger << log4cpp::Priority::DEBUG << "Forwarding status: " << int(forwarding_status_structure->status) << " reason code: " << int(forwarding_status_structure->reason_code); } else if (record_length == 4) { // We received 4 byte encoding from Cisco ASR9006 running IOS XR 6.4.2 // It's new format which was added by RFC bugfix: https://datatracker.ietf.org/doc/draft-ietf-opsawg-ipfix-fixes/12/ // We still have only single byte with information but whole structure is larger ipfix_forwarding_status_4_bytes_t forwarding_status{}; memcpy(&forwarding_status, data, record_length); // Decode numbers into forwarding statuses packet.forwarding_status = forwarding_status_from_integer(forwarding_status.status); flow_meta.received_forwarding_status = true; ipfix_forwarding_status++; // logger << log4cpp::Priority::DEBUG << "Forwarding status: " << int(forwarding_status.status) << " reason code: " << int(forwarding_status.reason_code); } else { // It must be exactly one byte ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_FORWARDING_STATUS: " << record_length; } } break; case IPFIX_DATALINK_FRAME_SIZE: if (record_length == 2) { uint16_t datalink_frame_size = 0; memcpy(&datalink_frame_size, data, record_length); flow_meta.data_link_frame_size = fast_ntoh(datalink_frame_size); } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_DATALINK_FRAME_SIZE: " << record_length; } } break; case IPFIX_DATALINK_FRAME_SECTION: { // Element 315: https://www.iana.org/assignments/ipfix/ipfix.xhtml // It's packet header as is in variable length encoding ipfix_inline_headers++; // This packet is ended using IPFIX variable length encoding and it may have two possible ways of length // encoding https://datatracker.ietf.org/doc/html/rfc7011#section-7 if (flow_meta.variable_field_length_encoding == variable_length_encoding_t::single_byte || flow_meta.variable_field_length_encoding == variable_length_encoding_t::two_byte) { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Packet header length: " << flow_meta.variable_field_length; } if (flow_meta.variable_field_length != 0) { const uint8_t* payload_shift = nullptr; if (flow_meta.variable_field_length_encoding == variable_length_encoding_t::single_byte) { payload_shift = data + sizeof(uint8_t); } else if (flow_meta.variable_field_length_encoding == variable_length_encoding_t::two_byte) { payload_shift = data + sizeof(uint8_t) + sizeof(uint16_t); } parser_options_t parser_options{}; parser_options.unpack_gre = false; parser_options.read_packet_length_from_ip_header = true; auto result = parse_raw_packet_to_simple_packet_full(payload_shift, flow_meta.variable_field_length, flow_meta.variable_field_length, flow_meta.nested_packet, parser_options); if (result != parser_code_t::success) { // Cannot decode data ipfix_inline_header_parser_error++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Cannot parse packet header with error: " << parser_code_to_string(result); } } else { // Successfully decoded data ipfix_inline_header_parser_success++; flow_meta.nested_packet_parsed = true; // logger << log4cpp::Priority::DEBUG << "IPFIX inline extracted packet: " << print_simple_packet(flow_meta.nested_packet); } } else { ipfix_inline_encoding_error++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Zero length variable fields are not supported"; } } } else { ipfix_inline_encoding_error++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unknown variable field encoding type"; } } break; } case IPFIX_FLOW_DIRECTION: // It should be 1 byte value if (record_length == 1) { uint8_t flow_direction = 0; memcpy(&flow_direction, data, record_length); // According to RFC only two values possible: https://www.iana.org/assignments/ipfix/ipfix.xhtml // 0x00: ingress flow // 0x01: egress flow // Juniper MX uses 255 to report unknown direction // std::cout << "Flow direction: " << int(flow_direction) << std::endl; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_FLOW_DIRECTION: " << record_length; } } break; case IPFIX_FLOW_END_REASON: // It should be 1 byte value if (record_length == 1) { uint8_t flow_end_reason = 0; memcpy(&flow_end_reason, data, record_length); // https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flow-end-reason if (flow_end_reason == 1) { ipfix_flows_end_reason_idle_timeout++; } else if (flow_end_reason == 2) { ipfix_flows_end_reason_active_timeout++; } else if (flow_end_reason == 3) { ipfix_flows_end_reason_end_of_flow_timeout++; } else if (flow_end_reason == 4) { ipfix_flows_end_reason_force_end_timeout++; } else if (flow_end_reason == 5) { ipfix_flows_end_reason_lack_of_resource_timeout++; } } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_FLOW_END_REASON: " << record_length; } } break; case IPFIX_FRAGMENT_IDENTIFICATION: // // Specification: https://www.rfc-editor.org/rfc/rfc5102.html#section-5.4.23 // // IPFIX uses 32 bit values to accommodate following cases: // - 16 bit IPv4 identification field https://www.rfc-editor.org/rfc/rfc791 // - 32 bit IPv6 identification field https://en.wikipedia.org/wiki/IPv6_packet#Fragment // // Juniper uses it on J MX platforms but they do not have much information about it: // https://www.juniper.net/documentation/us/en/software/junos/flow-monitoring/topics/concept/inline-sampling-overview.html // I asked https://t.me/dgubin about it // // I did review of dump from J MX and I can confirm that values for IPv4 do not exceed maximum value for uint16_t (65535) // // J MX is doing something fun with this field. I got dump in hands and in this dump of 42421 packets only 2337 have non zero value of this field. // Clearly they violate RFC and do not populate this field unconditionally as RFC dictates. // // I see cases like this which is very likely non first fragment of fragmented series of packets as we do not have ports: // Identification: 20203 ipv4:0 > ipv4:0 protocol: udp frag: 0 packets: 1 size: 352 bytes ip size: 352 bytes ttl: 0 sample ratio: 1 // // And I see packets like this which may be first packet in fragmented series of packets as we do indeed have ports here and packet length is high: // Identification: 2710 ipv4:53 > ipv4:45134 protocol: udp frag: 0 packets: 1 size: 1476 bytes ip size: 1476 bytes ttl: 0 sample ratio: 1 // // And majority of packets looks this way: // Identification: 0 ipv4:80 > ipv4:50179 protocol: tcp flags: ack frag: 0 packets: 1 size: 40 bytes ip size: 40 bytes ttl: 0 sample ratio: 1 // // We clearly can distinguish first fragmented packet and non first fragmented packet // // TODO: this logic must be enabled via flag only as this is non RFC compliant behavior and we need to have confirmation from J // // We have this guide from J: https://www.juniper.net/documentation/us/en/software/junos/flow-monitoring/topics/concept/services-ipfix-flow-aggregation-ipv6-extended-attributes.html // but it's written in exceptionally weird way and raises more questions then answers // // It's exactly 4 bytes if (record_length == 4) { uint32_t fragment_identification = 0; memcpy(&fragment_identification, data, record_length); fragment_identification = fast_ntoh(fragment_identification); // logger << log4cpp::Priority::INFO << "Fragment identification: " << fragment_identification; } else { ipfix_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_FRAGMENT_IDENTIFICATION: " << record_length; } } break; } return true; } // Read options data packet with known template bool ipfix_options_set_to_store(const uint8_t* packet, const ipfix_header_t* ipfix_header, const template_t* flow_template, const std::string& client_addres_in_string_format) { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Starting ipfix_options_set_to_store"; } // Skip scope fields, I really do not want to parse this information packet += flow_template->option_scope_length; uint32_t sampling_rate = 0; // Field shift in memory uint32_t offset = 0; // Sampling algorithm for exotic sampling types uint16_t sampling_selector_algorithm = 0; // We use these fields to work with systematic count-based Sampling Selector on Nokia uint32_t sampling_packet_space = 0; uint32_t sampling_packet_interval = 0; device_timeouts_t device_timeouts{}; for (const auto& elem : flow_template->records) { const uint8_t* data_shift = packet + offset; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Processing field type " << elem.record_type << " with field length " << elem.record_length; } // Time to extract sampling rate if (elem.record_type == IPFIX_SAMPLING_INTERVAL) { // RFC suggest that this field is 4 byte: https://www.iana.org/assignments/ipfix/ipfix.xhtml if (elem.record_length == 4) { uint32_t current_sampling_rate = 0; memcpy(¤t_sampling_rate, data_shift, elem.record_length); // TODO: we do not convert value to little endian as sampling update function expects big endian / network byte order sampling_rate = current_sampling_rate; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "4 byte encoded IPFIX_SAMPLING_INTERVAL sampling rate: " << sampling_rate << " from " << client_addres_in_string_format; } } else { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_SAMPLING_INTERVAL: " << elem.record_length; } ipfix_too_large_field++; } } else if (elem.record_type == IPFIX_SAMPLING_PACKET_INTERVAL) { // RFC suggest that this field is 4 byte: https://www.iana.org/assignments/ipfix/ipfix.xhtml if (elem.record_length == 4) { uint32_t current_sampling_packet_interval = 0; memcpy(¤t_sampling_packet_interval, data_shift, elem.record_length); current_sampling_packet_interval = fast_ntoh(current_sampling_packet_interval); // Well, we need this information to deal with systematic count-based Sampling Selector on Nokia sampling_packet_interval = current_sampling_packet_interval; // And we need this value to use as regular sampling rate on Cisco NSC // We need to return it to big endian again we sampling logic in IPFIX uses big endian / network byte order sampling_rate = fast_hton(sampling_packet_interval); } else { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for IPFIX_SAMPLING_PACKET_INTERVAL: " << elem.record_length; } ipfix_too_large_field++; } } else if (elem.record_type == IPFIX_SAMPLING_PACKET_SPACE) { // RFC requires this field to be 4 byte long if (elem.record_length == 4) { memcpy(&sampling_packet_space, data_shift, elem.record_length); sampling_packet_space = fast_ntoh(sampling_packet_space); } else { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpected size for IPFIX_SAMPLING_PACKET_SPACE: " << elem.record_length; } ipfix_too_large_field++; // We're OK to continue process, we should not stop it } } else if (elem.record_type == IPFIX_SAMPLING_SELECTOR_ALGORITHM) { // RFC requires this field to be 2 byte long // You can find all possible values for it here: https://www.iana.org/assignments/psamp-parameters/psamp-parameters.xhtml if (elem.record_length == 2) { memcpy(&sampling_selector_algorithm, data_shift, elem.record_length); sampling_selector_algorithm = fast_ntoh(sampling_selector_algorithm); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Decoded sampling selector algorithm " << sampling_selector_algorithm; } } else { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpected size for IPFIX_SAMPLING_SELECTOR_ALGORITM: " << elem.record_length; } ipfix_too_large_field++; // We're OK to continue process, we should not stop it } } else if (elem.record_type == IPFIX_ACTIVE_TIMEOUT) { uint16_t active_timeout = 0; // J MX204 with JunOS 19 encodes it with 2 bytes as RFC requires if (elem.record_length == 2) { memcpy(&active_timeout, data_shift, elem.record_length); active_timeout = fast_ntoh(active_timeout); ipfix_active_flow_timeout_received++; device_timeouts.active_timeout = active_timeout; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Got active timeout: " << active_timeout << " seconds"; } } else { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpected size for IPFIX_ACTIVE_TIMEOUT: " << elem.record_length; } ipfix_too_large_field++; } } else if (elem.record_type == IPFIX_INACTIVE_TIMEOUT) { uint16_t inactive_timeout = 0; // J MX204 with JunOS 19 encodes it with 2 bytes as RFC requires if (elem.record_length == 2) { memcpy(&inactive_timeout, data_shift, elem.record_length); inactive_timeout = fast_ntoh(inactive_timeout); ipfix_inactive_flow_timeout_received++; device_timeouts.inactive_timeout = inactive_timeout; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Got inactive timeout: " << inactive_timeout << " seconds"; } } else { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpected size for IPFIX_INACTIVE_TIMEOUT: " << elem.record_length; } ipfix_too_large_field++; } } offset += elem.record_length; } // Additional logic to deal with systematic count-based Sampling Selector on Nokia Nokia 7750 SR // https://www.rfc-editor.org/rfc/rfc5476.html#section-6.5.2.1 // We check that sampler selected non zero number of packets as additional sanity check that we deal with this // specific type of sampler and to avoid division by zero if (sampling_selector_algorithm == IPFIX_SAMPLER_TYPE_SYSTEMATIC_COUNT_BASED_SAMPLING && sampling_packet_interval != 0) { // We have seen following cases from Nokia: // Packet space: 999 packet interval 1 // Packet space: 9999 packet interval 1 // // Packet interval is the number of packets selected from whole packet space // // // We never seen packet interval which is not set to 1 but I prefer to cover this case too // For values of packet interval after 1 we need to divide whole amount of observed packets // (sampling_packet_space + sampling_packet_interval) by number of selected packets // uint32_t systematic_count_based_sampling_rate = uint32_t(double(sampling_packet_space + sampling_packet_interval) / double(sampling_packet_interval)); // Update sampling rate sampling_rate = fast_hton(systematic_count_based_sampling_rate); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Packet space: " << sampling_packet_space << " packet interval " << sampling_packet_interval << " sampling " << systematic_count_based_sampling_rate; } } update_ipfix_sampling_rate(sampling_rate, client_addres_in_string_format); // Update flow timeouts in our store update_device_flow_timeouts(device_timeouts, ipfix_per_device_flow_timeouts_mutex, ipfix_per_device_flow_timeouts, client_addres_in_string_format, netflow_protocol_version_t::ipfix); return true; } // That's kind of histogram emulation void increment_duration_counters_ipfix(int64_t duration) { if (duration == 0) { ipfix_duration_0_seconds++; } else if (duration <= 1) { ipfix_duration_less_1_seconds++; } else if (duration <= 2) { ipfix_duration_less_2_seconds++; } else if (duration <= 3) { ipfix_duration_less_3_seconds++; } else if (duration <= 5) { ipfix_duration_less_5_seconds++; } else if (duration <= 10) { ipfix_duration_less_10_seconds++; } else if (duration <= 15) { ipfix_duration_less_15_seconds++; } else if (duration <= 30) { ipfix_duration_less_30_seconds++; } else if (duration <= 60) { ipfix_duration_less_60_seconds++; } else if (duration <= 90) { ipfix_duration_less_90_seconds++; } else if (duration <= 180) { ipfix_duration_less_180_seconds++; } else { ipfix_duration_exceed_180_seconds++; } return; } // In case of success it fills fields in variable_length_encoding_info bool read_ipfix_variable_length_field(const uint8_t* packet, uint32_t offset, uint32_t set_length, variable_length_encoding_info_t& variable_length_encoding_info) { // We need to have at least one byte to read data if (offset + sizeof(uint8_t) > set_length) { logger << log4cpp::Priority::ERROR << "Attempt to read data after end of set for variable field length"; return false; } const uint8_t* field_length_ptr = (const uint8_t*)(packet + offset); if (*field_length_ptr == 0) { logger << log4cpp::Priority::ERROR << "Zero length variable fields are not supported"; ipfix_inline_encoding_error++; return false; } if (*field_length_ptr == 255) { // 255 is special and it means that packet length is encoded in two following bytes // Juniper PTX routers use this encoding even in case when packet length does not exceed 255 bytes // RFC reference https://datatracker.ietf.org/doc/html/rfc7011#page-37 // In this case, the first octet of the // Length field MUST be 255, and the length is carried in the second and // third octets, as shown in Figure S. // We need to have at least three bytes to read data if (offset + sizeof(uint8_t) + sizeof(uint16_t) > set_length) { logger << log4cpp::Priority::ERROR << "Attempt to read data after end of set for variable field length"; return false; } // Read 2 byte length by skipping placeholder byte with 255 const uint16_t* two_byte_field_length_ptr = (const uint16_t*)(packet + offset + sizeof(uint8_t)); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Two byte variable length encoding detected. Retrieved packet length: " << fast_ntoh(*two_byte_field_length_ptr); } // Pass variable payload length variable_length_encoding_info.variable_field_length = fast_ntoh(*two_byte_field_length_ptr); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Two byte variable length encoding detected. Retrieved packet length: " << variable_length_encoding_info.variable_field_length; } // Override field length with length extracted from two bytes + length of placeholder byte itself variable_length_encoding_info.record_full_length = variable_length_encoding_info.variable_field_length + sizeof(uint8_t) + sizeof(uint16_t); // Specify length encoding type as it's required for payload retrieval process variable_length_encoding_info.variable_field_length_encoding = variable_length_encoding_t::two_byte; } else { // Pass variable payload length variable_length_encoding_info.variable_field_length = *field_length_ptr; // Override field length with length extracted from leading byte variable_length_encoding_info.record_full_length = variable_length_encoding_info.variable_field_length + sizeof(uint8_t); // Specify length encoding type as it's required for payload retrieval process variable_length_encoding_info.variable_field_length_encoding = variable_length_encoding_t::single_byte; } // Ensure that we have enough space to read whole variable field if (offset + variable_length_encoding_info.record_full_length > set_length) { logger << log4cpp::Priority::ERROR << "Attempt to read data after end of set for variable field length"; return false; } return true; } // This function reads data set using passed template // In case of irrecoverable errors it returns false bool ipfix_data_set_to_store(const uint8_t* packet_ptr, const ipfix_header_t* ipfix_header, uint32_t set_maximum_length, const template_t* field_template, uint32_t client_ipv4_address, uint32_t& set_length, const std::string& client_addres_in_string_format) { simple_packet_t packet; packet.source = NETFLOW; packet.arrival_time = current_inaccurate_time; packet.agent_ipv4_address = client_ipv4_address; // We use shifted values and should process only zeroed values // because we are working with little and big endian data in same time packet.number_of_packets = 0; packet.ts.tv_sec = ipfix_header->get_time_sec_host_byte_order(); { std::lock_guard lock(ipfix_sampling_rates_mutex); auto itr = ipfix_sampling_rates.find(client_addres_in_string_format); if (itr == ipfix_sampling_rates.end()) { // Use global value packet.sample_ratio = fastnetmon_global_configuration.netflow_sampling_ratio; } else { packet.sample_ratio = itr->second; } } // By default, assume IPv4 traffic here // But code below can switch it to IPv6 packet.ip_protocol_version = 4; //-V1048 // Place to keep meta information which is not needed in simple_simple_packet_t structure netflow_meta_info_t flow_meta; uint32_t offset = 0; for (auto iter = field_template->records.begin(); iter != field_template->records.end(); iter++) { uint32_t record_type = iter->record_type; uint32_t record_length = iter->record_length; // logger << log4cpp::Priority::DEBUG << "Reading record with type " << record_type << " and length " << record_length; if (record_length == 65535) { // OK, we're facing variable length field and it's damn complex // We need to calculate field length here and then use this length in ipfix_record_to_flow variable_length_encoding_info_t variable_length_encoding_info{}; bool read_ipfix_variable_length_field_result = read_ipfix_variable_length_field(packet_ptr, offset, set_maximum_length, variable_length_encoding_info); if (!read_ipfix_variable_length_field_result) { return false; } // Copy meta informatiom about variable length encoding to flow_meta // TODO: I'm not sure that this information is needed in flow_meta as it's specific only for field we parse right now flow_meta.variable_field_length_encoding = variable_length_encoding_info.variable_field_length_encoding; flow_meta.variable_field_length = variable_length_encoding_info.variable_field_length; // Override record_length as we need full length to jump to another record record_length = variable_length_encoding_info.record_full_length; } // We do not need this check when we have only fixed length fields in template // but this function is versatile and must handle all cases. if (offset + record_length > set_maximum_length) { logger << log4cpp::Priority::ERROR << "Attempt to read data after end of set. Offset: " << offset << " record length: " << record_length << " set_maximum_length: " << set_maximum_length; return false; } bool ipfix_record_to_flow_result = ipfix_record_to_flow(record_type, record_length, packet_ptr + offset, packet, flow_meta); // In case of serious errors we stop loop completely if (!ipfix_record_to_flow_result) { return false; } offset += record_length; } // At this moment offset carries full length of all fields set_length = offset; // If we were able to decode nested packet then it means that it was Netflow Lite and we can overwrite information in packet if (flow_meta.nested_packet_parsed) { // Override most of the fields from nested packet as we need to use them instead override_packet_fields_from_nested_packet(packet, flow_meta.nested_packet); } if (false) { // // For Juniper routers we need fancy logic to mark packets as dropped as it does not use RFC compliant IPFIX field for it // // // The only reliable information we have from Juniper documentation is about Netflow v9 // https://apps.juniper.net/feature-explorer/feature-info.html?fKey=7679&fn=Enhancements%20to%20inline%20flow%20monitoring // and we have no idea how it behaves in IPFIX mode. // // I think previously we had Juniper routers which set output interface to zero and both bgp_next_hop_ipv4 and // ip_next_hop_ipv4 to zero values to report dropped and we checked only bgp_next_hop_ipv4 to identify dropped // traffic. It worked well enough until we got flows explained below where bgp_next_hop_ipv4 is not 0.0.0.0 but // ip_next_hop_ipv4 and output interface were set to zeroes. // // In May 2023 got dumps in Google drive "MX10003 and MX 480 dropped traffic" which confirms that Juniper MX // 10003 / MX480 with JUNOS 20.4R3-S4.8 encode it using zero output interface and zero ip_next_hop_ipv4. In same // time these dumps have bgp_next_hop_ipv4 set to real non zero value of next router. To address this issue we // added alternative section to check for zeroe // // I posted question on LinkedIN: https://www.linkedin.com/feed/update/urn:li:activity:7062447441895141376/ // // We will apply it only if we have no forwarding_status in packet if (!flow_meta.received_forwarding_status) { // We need to confirm that TWO rules are TRUE: // - Output interface is 0 // - Next hop for IPv4 is set and set to 0 OR next hop for IPv6 set and set to zero if (packet.output_interface == 0 && ((flow_meta.bgp_next_hop_ipv4_set && flow_meta.bgp_next_hop_ipv4 == 0) || (flow_meta.ip_next_hop_ipv4_set && flow_meta.ip_next_hop_ipv4 == 0) || (flow_meta.bgp_next_hop_ipv6_set && is_zero_ipv6_address(flow_meta.bgp_next_hop_ipv6)))) { packet.forwarding_status = forwarding_status_t::dropped; ipfix_marked_zero_next_hop_and_zero_output_as_dropped++; } } } // std::cout << "bgp next hop: " << convert_ip_as_uint_to_string(flow_meta.bgp_next_hop_ipv4) << " set " << flow_meta.bgp_next_hop_ipv4_set // << " " << print_ipv6_address(flow_meta.bgp_next_hop_ipv6) << " set " << flow_meta.bgp_next_hop_ipv6_set << " output interface: " << packet.output_interface << std::endl; netflow_ipfix_all_protocols_total_flows++; ipfix_total_flows++; // We may have cases like this from previous step: // :0000:443 > :0000:61444 protocol: tcp flags: psh,ack frag: 0 packets: 1 size: 205 bytes ip size: 205 bytes ttl: // 0 sample ratio: 1000 It happens when router sends IPv4 and zero IPv6 fields in same packet if (packet.ip_protocol_version == 6 && is_zero_ipv6_address(packet.src_ipv6) && is_zero_ipv6_address(packet.dst_ipv6) && packet.src_ip != 0 && packet.dst_ip != 0) { ipfix_protocol_version_adjustments++; packet.ip_protocol_version = 4; } if (packet.ip_protocol_version == 4) { ipfix_total_ipv4_flows++; } else if (packet.ip_protocol_version == 6) { ipfix_total_ipv6_flows++; } double duration_float = packet.flow_end - packet.flow_start; // Well, it does happen with Juniper QFX if (duration_float < 0) { ipfix_duration_negative++; // I see no reasons to track duration of such cases because they're definitely broken } else { // Covert milliseconds to seconds duration_float = duration_float / 1000; int64_t duration = int64_t(duration_float); // Increments duration counters increment_duration_counters_ipfix(duration); // logger<< log4cpp::Priority::INFO<< "Flow start: " << packet.flow_start << " end: " << packet.flow_end << " duration: " << duration; } // logger<< log4cpp::Priority::INFO<<"src asn: " << packet.src_asn << " " << "dst asn: " << packet.dst_asn; // logger<< log4cpp::Priority::INFO<<"output: " << packet.output_interface << " " << " input: " << packet.input_interface; // Logical sources of this logic are unknown but I'm sure we had reasons to do so if (packet.protocol == IPPROTO_ICMP) { // Explicitly set ports to zeros even if device sent something in these fields packet.source_port = 0; packet.destination_port = 0; } // pass data to FastNetMon netflow_process_func_ptr(packet); return true; } bool process_ipfix_regular_data_set(const uint8_t* packet, uint32_t offset, uint32_t set_id, uint32_t set_length, const ipfix_header_t* ipfix_header, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address, const template_t* field_template); bool process_ipfix_options_data_set(const uint8_t* packet, uint32_t offset, uint32_t set_id, uint32_t set_length, const ipfix_header_t* ipfix_header, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address, const template_t* field_template, const uint8_t* set_end); bool process_ipfix_data_set(const uint8_t* packet, uint32_t set_length, const ipfix_header_t* ipfix_header, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address) { const ipfix_set_header_common_t* set_header = (const ipfix_set_header_common_t*)packet; if (set_length < sizeof(ipfix_set_header_common_t)) { logger << log4cpp::Priority::ERROR << "Too short IPFIX set with not enough space for set header: " << set_length << " Agent: " << client_addres_in_string_format; return false; } // Store packet end, it's useful for sanity checks const uint8_t* set_end = packet + set_length; uint32_t set_id = set_header->get_set_id_host_byte_order(); const template_t* field_template = peer_find_template(global_ipfix_templates, global_ipfix_templates_mutex, source_id, set_id, client_addres_in_string_format); if (field_template == NULL) { ipfix_packets_with_unknown_templates++; logger << log4cpp::Priority::DEBUG << "We don't have a IPFIX template for set_id: " << set_id << " client " << client_addres_in_string_format << " source_id: " << source_id << " but it's not an error if this message disappears in some time " "seconds. We need some time to learn them"; return false; } if (field_template->records.empty()) { logger << log4cpp::Priority::ERROR << "There are no records in IPFIX template. Agent: " << client_addres_in_string_format; return false; } uint32_t offset = sizeof(ipfix_set_header_common_t); if (field_template->type == netflow_template_type_t::Data) { bool regular_ipfix_set_result = process_ipfix_regular_data_set(packet, offset, set_id, set_length, ipfix_header, source_id, client_addres_in_string_format, client_ipv4_address, field_template); if (!regular_ipfix_set_result) { return false; } } else if (field_template->type == netflow_template_type_t::Options) { bool options_ipfix_set_result = process_ipfix_options_data_set(packet, offset, set_id, set_length, ipfix_header, source_id, client_addres_in_string_format, client_ipv4_address, field_template, set_end); if (!options_ipfix_set_result) { return false; } } return true; } // Process regular data set which carries options data bool process_ipfix_options_data_set(const uint8_t* packet, uint32_t offset, uint32_t set_id, uint32_t set_length, const ipfix_header_t* ipfix_header, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address, const template_t* field_template, const uint8_t* set_end) { ipfix_options_packet_number++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Starting process_ipfix_options_data_set for set_length " << set_length; } if (field_template->ipfix_variable_length_elements_used) { // We do not have logic to decode such encoding yet, it's used by Arista and we have dumps in lab logger << log4cpp::Priority::ERROR << "IPFIX variable field encoding is not supported. Agent: " << client_addres_in_string_format << " IPFIX sequence: " << ipfix_header->get_package_sequence_host_byte_order() << " set id: " << set_id; // We intentionally return true here as it's not internal consistency error and we need to continue processing other sets in packet return true; } else { // Check that we will not read outside of packet if (packet + offset + field_template->total_length > set_end) { logger << log4cpp::Priority::ERROR << "We tried to read data outside packet for IPFIX options. " << "Agent: " << client_addres_in_string_format << " IPFIX sequence: " << ipfix_header->get_package_sequence_host_byte_order() << " set id: " << set_id << " set_length: " << set_length << " template total length: " << field_template->total_length << " ipfix_variable_length_elements_used: " << field_template->ipfix_variable_length_elements_used; return false; } // Process options packet ipfix_options_set_to_store(packet + offset, ipfix_header, field_template, client_addres_in_string_format); } return true; } // Process regular data set which usually carries flows bool process_ipfix_regular_data_set(const uint8_t* packet, uint32_t offset, uint32_t set_id, uint32_t set_length, const ipfix_header_t* ipfix_header, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address, const template_t* field_template) { if (field_template->ipfix_variable_length_elements_used) { // When we have variable length fields we need to use different logic which relies on flow length calculated during process of reading flow // Get clean sets length to use it as limit for our parser ssize_t current_set_length_no_header = set_length - sizeof(ipfix_set_header_common_t); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "IPFIX variable field element was used"; } // Where all flows start in packet const uint8_t* flow_section_start = packet + sizeof(ipfix_set_header_common_t); // Offset where flow starts uint32_t flow_offset = 0; // How much data we have in current flow set uint32_t maximum_data_available_to_read = current_set_length_no_header; // Run this loop until flow_offset reaches end of packet while (flow_offset < current_set_length_no_header) { // When variable fields present we need to read all fields before getting total length of flow uint32_t read_flow_length = 0; // In many cases we have just single flow per UDP packet but Juniper PTX uses multiple flows per packet bool floset_processing_result = ipfix_data_set_to_store(flow_section_start + flow_offset, ipfix_header, maximum_data_available_to_read, field_template, client_ipv4_address, read_flow_length, client_addres_in_string_format); // If we cannot process this set then we must stop processing here because we need correct value of set_length to jump to next record if (!floset_processing_result) { return false; } if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Total flow length: " << read_flow_length; } // Shift set offset by length of data read in this iteration flow_offset += read_flow_length; // And in the same time reduce amount of available to read data maximum_data_available_to_read -= read_flow_length; // Check if amount of data we still have in packet is less then minimum length of data record // Please note that as we use variable length fields we do not know exact length // Instead it's minimum length if (maximum_data_available_to_read < field_template->total_length) { // It may be padding but sadly we do not have any reasonable explanation about padding length for data records in RFC // https://datatracker.ietf.org/doc/html/rfc7011 // Cisco ASR 9000 is doing 2 byte padding with presence of variable field records if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Got " << maximum_data_available_to_read << " byte padding on end of data set"; } // I think we must report it as it may be curious case to look on and capture pcaps if (maximum_data_available_to_read > 5) { logger << log4cpp::Priority::WARN << "Got too long " << maximum_data_available_to_read << " on end of data set"; } const uint8_t* padding_start_in_packet = (const uint8_t*)(flow_section_start + flow_offset); bool all_padding_bytes_are_zero = are_all_padding_bytes_are_zero(padding_start_in_packet, maximum_data_available_to_read); if (all_padding_bytes_are_zero) { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "All padding bytes are zero, feel free to correctly stop processing"; } // Stop loop correctly without triggering error break; } else { logger << log4cpp::Priority::ERROR << "Non zero " << maximum_data_available_to_read << " padding bytes, semething is wrong with packet"; // We have to report error return false; } } else { // All fine, we can try reading next record } } } else { // We use this logic only if we have only fixed length field specifiers in template // Check that template total length is not zero as we're going to divide by it if (field_template->total_length == 0) { logger << log4cpp::Priority::ERROR << "Zero IPFIX template length is not valid " << "client " << client_addres_in_string_format << " source_id: " << source_id; return false; } // This logic is pretty reliable but it works only if we do not have variable sized fields in template // In that case it's completely not applicable // But I prefer to keep it as it's very predictable and works great for fields fields // Templates with only fixed fields are 99% of our installations and variable fields are very rare // Consider this path as attempt to optimise things uint32_t number_of_records = (set_length - offset) / field_template->total_length; // We need to calculate padding value // IPFIX RFC explains it following way: // https://datatracker.ietf.org/doc/html/rfc7011?ref=pavel.network#section-3.3.1 uint32_t set_padding = (set_length - offset) % field_template->total_length; // Very likely data will be aligned by 4 byte boundaries and will have padding 1, 2, 3 bytes // To be on safe side we assume that padding may be up to 7 bytes to achieve 8 byte boundaries // All other values may be sign of some kind of issues. For example, it may be template conflicts // https://pavel.network/its-just-wrong-to-update-ipfix-templates/ if (set_padding > 7) { ipfix_sets_with_anomaly_padding++; } if (number_of_records > 0x4000) { logger << log4cpp::Priority::ERROR << "Very high number of IPFIX data records in set " << number_of_records << " Agent: " << client_addres_in_string_format << " set template length: " << field_template->total_length; return false; } if (number_of_records == 0) { logger << log4cpp::Priority::ERROR << "Unexpected zero number of sets " << " agent: " << client_addres_in_string_format << " set template length: " << field_template->total_length << " set length " << set_length << " source_id " << source_id << " set_id: " << set_id; return false; } for (uint32_t record_index = 0; record_index < number_of_records; record_index++) { // We do not use it as we can use total_length directly instead of calculating it uint32_t read_data_length_discarded = 0; // We apply constraint that maximum potential length of flow set cannot exceed length of all fields in // template In this case we have no fields with variable length which may affect it and we're safe // We do not check response code as we can jump to next flow even if previous one failed ipfix_data_set_to_store(packet + offset, ipfix_header, field_template->total_length, field_template, client_ipv4_address, read_data_length_discarded, client_addres_in_string_format); offset += field_template->total_length; } } return true; } bool process_ipfix_sets(uint32_t offset, const uint8_t* packet, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address, uint32_t ipfix_packet_length, const ipfix_header_t* ipfix_header); // Process IPFIX packet bool process_ipfix_packet(const uint8_t* packet, uint32_t udp_packet_length, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address) { ipfix_total_packets++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Starting reading IPFIX UDP packet with length " << udp_packet_length; } // Ensure that we have enough bytes to read IPFIX packet header if (udp_packet_length < sizeof(ipfix_header_t)) { logger << log4cpp::Priority::ERROR << "Packet is too short to accommodate IPFIX header " << udp_packet_length << " bytes which requires at least " << sizeof(ipfix_header_t) << " bytes"; return false; } const ipfix_header_t* ipfix_header = (const ipfix_header_t*)packet; // In compare with Netflow v9 IPFIX uses packet length instead of explicitly specified number of sets // https://datatracker.ietf.org/doc/html/rfc7011#section-3.1 // Total length of the IPFIX Message, measured in octets, including Message Header and Set(s). uint32_t ipfix_packet_length = ipfix_header->get_length_host_byte_order(); if (udp_packet_length == ipfix_packet_length) { // Under normal circumstances udp_packet_length must be equal to ipfix_packet_length } else { // If they're different we need to do more careful checks if (udp_packet_length > ipfix_packet_length) { // Theoretically it may happen if we have some padding on the end of packet if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "udp_packet_length exceeds ipfix_packet_length, suspect padding"; } ipfix_packets_with_padding++; } // And this case we cannot tolerate if (udp_packet_length < ipfix_packet_length) { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "UDP packet it shorter (" << udp_packet_length << ")" << " then IPFIX data (" << ipfix_packet_length << "). Malformed packet"; } return false; } } // We will start reading IPFIX sets right after IPFIX header uint32_t offset = sizeof(*ipfix_header); // This function will read all kinds of sets from packet bool result = process_ipfix_sets(offset, packet, client_addres_in_string_format, client_ipv4_address, ipfix_packet_length, ipfix_header); if (!result) { logger << log4cpp::Priority::ERROR << "process_ipfix_sets returned error"; return false; } return true; } // This function will read all kinds of sets from packet bool process_ipfix_sets(uint32_t offset, const uint8_t* packet, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address, uint32_t ipfix_packet_length, const ipfix_header_t* ipfix_header) { // We will use it to count number of sets uint64_t set_sequence_number = 0; uint32_t source_id = ipfix_header->get_source_id_host_byte_order(); // Yes, it's infinite loop but we apply boundaries inside to limit it while (true) { set_sequence_number++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Reading set number " << set_sequence_number; } // We limit number of flow sets in packet and also use it for infinite loop prevention if (set_sequence_number > sets_per_packet_maximum_number) { logger << log4cpp::Priority::ERROR << "Infinite loop prevention triggered or we have so many sets inside IPFIX packet"; return false; } if (offset >= ipfix_packet_length) { logger << log4cpp::Priority::ERROR << "We tried to read from address outside of IPFIX packet agent IP: " << client_addres_in_string_format; return false; } // Check that we have enough space in packet to read set header if (offset + sizeof(ipfix_set_header_common_t) > ipfix_packet_length) { logger << log4cpp::Priority::ERROR << "Flowset is too short: we do not have space for set header. " << "IPFIX packet agent IP:" << client_addres_in_string_format << " set number: " << set_sequence_number << " offset: " << offset << " packet_length: " << ipfix_packet_length; return false; } const ipfix_set_header_common_t* set_header = (const ipfix_set_header_common_t*)(packet + offset); uint32_t set_id = set_header->get_set_id_host_byte_order(); uint32_t set_length = set_header->get_length_host_byte_order(); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Reading set ID: " << set_id << " with length " << set_length; } // One more check to ensure that we have enough space in packet to read whole set if (offset + set_length > ipfix_packet_length) { logger << log4cpp::Priority::ERROR << "We tried to read from address outside IPFIX packet set agent IP: " << client_addres_in_string_format; return false; } switch (set_id) { case IPFIX_TEMPLATE_SET_ID: ipfix_data_templates_number++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Starting process_ipfix_data_template_set"; } if (!process_ipfix_data_template_set(packet + offset, set_length, source_id, client_addres_in_string_format)) { return false; } break; case IPFIX_OPTIONS_SET_ID: ipfix_options_templates_number++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Starting process_ipfix_options_template_set"; } if (!process_ipfix_options_template_set(packet + offset, set_length, source_id, client_addres_in_string_format)) { return false; } break; default: if (set_id < IPFIX_MIN_RECORD_SET_ID) { logger << log4cpp::Priority::ERROR << "Received unknown IPFIX reserved set type " << set_id; break; // interrupts only switch! } ipfix_data_packet_number++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Starting process_ipfix_data"; } if (!process_ipfix_data_set(packet + offset, set_length, ipfix_header, source_id, client_addres_in_string_format, client_ipv4_address)) { return false; } break; } // Shift on length of processed set offset += set_length; if (offset == ipfix_packet_length) { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Interrupt IPFIX set reader logic as offset reached end of IPFIX packet"; } break; } } return true; } void update_ipfix_sampling_rate(uint32_t sampling_rate, const std::string& client_addres_in_string_format) { if (sampling_rate == 0) { return; } // NB! Incoming sampling rate is big endian / network byte order auto new_sampling_rate = fast_ntoh(sampling_rate); ipfix_custom_sampling_rate_received++; logger << log4cpp::Priority::DEBUG << "I extracted sampling rate: " << new_sampling_rate << " for " << client_addres_in_string_format; bool any_changes_for_sampling = false; { // Replace old sampling rate value std::lock_guard lock(ipfix_sampling_rates_mutex); auto known_sampling_rate = ipfix_sampling_rates.find(client_addres_in_string_format); if (known_sampling_rate == ipfix_sampling_rates.end()) { // We had no sampling rates before ipfix_sampling_rates[client_addres_in_string_format] = new_sampling_rate; ipfix_sampling_rate_changes++; logger << log4cpp::Priority::INFO << "Learnt new IPFIX sampling rate " << new_sampling_rate << " for " << client_addres_in_string_format; any_changes_for_sampling = true; } else { auto old_sampling_rate = known_sampling_rate->second; if (old_sampling_rate != new_sampling_rate) { ipfix_sampling_rates[client_addres_in_string_format] = new_sampling_rate; ipfix_sampling_rate_changes++; logger << log4cpp::Priority::INFO << "Detected IPFIX sampling rate change from " << old_sampling_rate << " to " << new_sampling_rate << " for " << client_addres_in_string_format; any_changes_for_sampling = true; } } } } pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/ipfix_collector.hpp000066400000000000000000000006551520703010000267710ustar00rootroot00000000000000#pragma once std::vector get_ipfix_sampling_rates(); std::vector get_ipfix_stats(); bool process_ipfix_packet(const uint8_t* packet, uint32_t udp_packet_length, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address); void load_ipfix_template_cache(); void load_ipfix_sampling_cache(); pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/ipfix_metrics.hpp000066400000000000000000000152051520703010000264460ustar00rootroot00000000000000#pragma once std::string ipfix_marked_zero_next_hop_and_zero_output_as_dropped_desc = "IPFIX flow was marked as dropped from interface and next hop information"; uint64_t ipfix_marked_zero_next_hop_and_zero_output_as_dropped = 0; std::string ipfix_total_packets_desc = "Total number of IPFIX UDP packets received"; uint64_t ipfix_total_packets = 0; std::string ipfix_total_flows_desc = "Total number of IPFIX flows (multiple in each packet)"; uint64_t ipfix_total_flows = 0; std::string ipfix_total_ipv4_flows_desc = "Total number of IPFIX IPv4 flows (multiple in each packet)"; uint64_t ipfix_total_ipv4_flows = 0; std::string ipfix_active_flow_timeout_received_desc = "Total number of received active IPFIX flow timeouts"; uint64_t ipfix_active_flow_timeout_received = 0; std::string ipfix_inactive_flow_timeout_received_desc = "Total number of received inactive IPFIX flow timeouts"; uint64_t ipfix_inactive_flow_timeout_received = 0; std::string ipfix_total_ipv6_flows_desc = "Total number of IPFIX IPv6 flows (multiple in each packet)"; uint64_t ipfix_total_ipv6_flows = 0; std::string ipfix_sampling_rate_changes_desc = "How much times we changed sampling rate for same agent. As change we " "also count when we received it for the first time"; uint64_t ipfix_sampling_rate_changes = 0; std::string ipfix_duration_0_seconds_desc = "IPFIX flows with duration 0 seconds"; uint64_t ipfix_duration_0_seconds = 0; std::string ipfix_duration_less_1_seconds_desc = "IPFIX flows with duration less then 1 seconds"; uint64_t ipfix_duration_less_1_seconds = 0; std::string ipfix_duration_less_2_seconds_desc = "IPFIX flows with duration less then 2 seconds"; uint64_t ipfix_duration_less_2_seconds = 0; std::string ipfix_duration_less_3_seconds_desc = "IPFIX flows with duration less then 3 seconds"; uint64_t ipfix_duration_less_3_seconds = 0; std::string ipfix_duration_less_5_seconds_desc = "IPFIX flows with duration less then 5 seconds"; uint64_t ipfix_duration_less_5_seconds = 0; std::string ipfix_duration_less_10_seconds_desc = "IPFIX flows with duration less then 10 seconds"; uint64_t ipfix_duration_less_10_seconds = 0; std::string ipfix_duration_less_15_seconds_desc = "IPFIX flows with duration less then 15 seconds"; uint64_t ipfix_duration_less_15_seconds = 0; std::string ipfix_duration_less_30_seconds_desc = "IPFIX flows with duration less then 30 seconds"; uint64_t ipfix_duration_less_30_seconds = 0; std::string ipfix_duration_less_60_seconds_desc = "IPFIX flows with duration less then 60 seconds"; uint64_t ipfix_duration_less_60_seconds = 0; std::string ipfix_duration_less_90_seconds_desc = "IPFIX flows with duration less then 90 seconds"; uint64_t ipfix_duration_less_90_seconds = 0; std::string ipfix_duration_less_180_seconds_desc = "IPFIX flows with duration less then 180 seconds"; uint64_t ipfix_duration_less_180_seconds = 0; std::string ipfix_duration_exceed_180_seconds_desc = "IPFIX flows with duration more then 180 seconds"; uint64_t ipfix_duration_exceed_180_seconds = 0; std::string ipfix_forwarding_status_desc = "Number of IPFIX flows with forwarding status provided"; uint64_t ipfix_forwarding_status = 0; std::string ipfix_custom_sampling_rate_received_desc = "IPFIX customer sampling rates received"; uint64_t ipfix_custom_sampling_rate_received = 0; std::string ipfix_duration_negative_desc = "IPFIX packets with negative duration, it may happen when vendor does not implement protocol correctly"; uint64_t ipfix_duration_negative = 0; std::string ipfix_data_packet_number_desc = "IPFIX data packets number"; uint64_t ipfix_data_packet_number = 0; std::string ipfix_data_templates_number_desc = "IPFIX data templates number"; uint64_t ipfix_data_templates_number = 0; std::string ipfix_options_templates_number_desc = "IPFIX options templates number"; uint64_t ipfix_options_templates_number = 0; std::string ipfix_options_packet_number_desc = "IPFIX options data packets number"; uint64_t ipfix_options_packet_number = 0; std::string ipfix_packets_with_unknown_templates_desc = "Number of dropped IPFIX packets due to unknown template in message"; uint64_t ipfix_packets_with_unknown_templates = 0; // https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flow-end-reason std::string ipfix_flows_end_reason_idle_timeout_desc = "IPFIX flows finished by idle timeout"; uint64_t ipfix_flows_end_reason_idle_timeout = 0; std::string ipfix_flows_end_reason_active_timeout_desc = "IPFIX flows finished by active timeout"; uint64_t ipfix_flows_end_reason_active_timeout = 0; std::string ipfix_flows_end_reason_end_of_flow_timeout_desc = "IPFIX flows finished by end of flow timeout"; uint64_t ipfix_flows_end_reason_end_of_flow_timeout = 0; std::string ipfix_flows_end_reason_force_end_timeout_desc = "IPFIX flows finished by force end timeout"; uint64_t ipfix_flows_end_reason_force_end_timeout = 0; std::string ipfix_flows_end_reason_lack_of_resource_timeout_desc = "IPFIX flows finished by lack of resources"; uint64_t ipfix_flows_end_reason_lack_of_resource_timeout = 0; std::string ipfix_sets_with_anomaly_padding_desc = "IPFIX sets with anomaly padding more then 7 bytes"; uint64_t ipfix_sets_with_anomaly_padding = 0; std::string ipfix_template_data_updates_desc = "Count times when template data actually changed for IPFIX"; uint64_t ipfix_template_data_updates = 0; std::string ipfix_protocol_version_adjustments_desc = "Number of IPFIX flows with re-classified protocol version"; uint64_t ipfix_protocol_version_adjustments = 0; std::string ipfix_too_large_field_desc = "We increment these counters when field we use to store particular type of " "IPFIX record is smaller than we actually received from device"; uint64_t ipfix_too_large_field = 0; std::string ipfix_inline_header_parser_error_desc = "IPFIX inline header parser errors"; uint64_t ipfix_inline_header_parser_error = 0; std::string ipfix_inline_header_parser_success_desc = "IPFIX inline header parser success"; uint64_t ipfix_inline_header_parser_success = 0; std::string ipfix_inline_encoding_error_desc = "IPFIX inline encoding issues"; uint64_t ipfix_inline_encoding_error = 0; std::string ipfix_inline_headers_desc = "Total number of headers in IPFIX received"; uint64_t ipfix_inline_headers = 0; std::string ipfix_packets_with_padding_desc = "Total number of IPFIX packets with padding"; uint64_t ipfix_packets_with_padding = 0; pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow.cpp000066400000000000000000000000271520703010000252460ustar00rootroot00000000000000#include "netflow.hpp" pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow.hpp000066400000000000000000000017741520703010000252650ustar00rootroot00000000000000// Netflow packet definitions #pragma once #include #include #include enum class netflow_protocol_version_t { netflow_v5, netflow_v9, ipfix }; // Common header fields class __attribute__((__packed__)) netflow_header_common_t { public: uint16_t version = 0; uint16_t flows = 0; }; // This class carries mapping between interface ID and human friendly interface name class interface_id_to_name_t { public: uint32_t interface_id = 0; std::string interface_description{}; }; // Active timeout for IPFIX class device_timeouts_t { public: // Both values use seconds std::optional active_timeout = 0; std::optional inactive_timeout = 0; bool operator!=(const device_timeouts_t& rhs) const { return !(*this == rhs); } // We generate default == operator which compares each field in class using standard compare operators for each class bool operator==(const device_timeouts_t& rhs) const = default; }; pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_collector.cpp000066400000000000000000000665731520703010000273360ustar00rootroot00000000000000/* netflow plugin body */ // TODO: add timestamp to netflow templates stored at disk // TODO: do not kill disk with netflow template writes to disk #ifdef _WIN32 #include #include // sockaddr_in6 #include // getaddrinfo #else #include #include #include #include #endif #include #include #include #include #include "../fast_library.hpp" #include "../ipfix_fields/ipfix_rfc.hpp" #include "../all_logcpp_libraries.hpp" #include "../fastnetmon_plugin.hpp" #include "netflow.hpp" // Protocol specific things #include "netflow_v5.hpp" #include "netflow_v9.hpp" #include "netflow_template.hpp" #include "netflow_collector.hpp" #include #include #include #include // For Netflow lite parsing #include "../simple_packet_parser_ng.hpp" #include #include "../fastnetmon_configuration_scheme.hpp" #include "ipfix_collector.hpp" #include "netflow_v5_collector.hpp" #include "netflow_v9_collector.hpp" #include "netflow_meta_info.hpp" // Get it from main programme extern log4cpp::Category& logger; extern fastnetmon_configuration_t fastnetmon_global_configuration; // Per router packet counters std::mutex netflow5_packets_per_router_mutex; std::map netflow5_packets_per_router; std::mutex netflow9_packets_per_router_mutex; std::map netflow9_packets_per_router; std::mutex ipfix_packets_per_router_mutex; std::map ipfix_packets_per_router; uint64_t flows_per_packet_maximum_number = 256; // Counters section start std::string netflow_ipfix_total_ipv4_packets_desc = "Total number of Netflow or IPFIX UDP packets received over IPv4 protocol"; uint64_t netflow_ipfix_total_ipv4_packets = 0; std::string netflow_ipfix_total_ipv6_packets_desc = "Total number of Netflow or IPFIX UDP packets received over IPv6 protocol"; uint64_t netflow_ipfix_total_ipv6_packets = 0; std::string netflow_ipfix_total_packets_desc = "Total number of Netflow or IPFIX UDP packets received"; uint64_t netflow_ipfix_total_packets = 0; std::string netflow_ipfix_all_protocols_total_flows_desc = "Total number of flows summarized for all kinds of Netflow and IPFIX"; uint64_t netflow_ipfix_all_protocols_total_flows = 0; std::string netflow_ipfix_udp_packet_drops_desc = "Number of UDP packets dropped by system on our socket"; uint64_t netflow_ipfix_udp_packet_drops = 0; std::string netflow_ipfix_unknown_protocol_version_desc = "Number of packets with unknown Netflow version. In may be sign that some another protocol like sFlow is being " "send to Netflow or IPFIX port"; uint64_t netflow_ipfix_unknown_protocol_version = 0; std::string template_update_attempts_with_same_template_data_desc = "Number of templates received with same data as inside known by us"; uint64_t template_update_attempts_with_same_template_data = 0; std::string template_netflow_ipfix_disk_writes_desc = "Number of times when we write Netflow or ipfix templates to disk"; uint64_t template_netflow_ipfix_disk_writes = 0; std::string netflow_ignored_long_flows_desc = "Number of flows which exceed specified limit in configuration"; uint64_t netflow_ignored_long_flows = 0; // END of counters section void increment_duration_counters_ipfix(int64_t duration); // We limit number of flowsets in packet Netflow v9 / IPFIX packets with some reasonable number to reduce possible attack's surface and reduce probability of infinite loop uint64_t sets_per_packet_maximum_number = 256; // TODO: add per source uniq templates support process_packet_pointer netflow_process_func_ptr = NULL; std::vector get_netflow_stats() { std::vector system_counter; // Netflow v5 std::vector netflow_v5_stats = get_netflow_v5_stats(); // Append Netflow v5 stats system_counter.insert(system_counter.end(), netflow_v5_stats.begin(), netflow_v5_stats.end()); // Get Netflow v9 stats std::vector netflow_v9_stats = get_netflow_v9_stats(); // Append Netflow v9 stats system_counter.insert(system_counter.end(), netflow_v9_stats.begin(), netflow_v9_stats.end()); // Get IPFIX stats std::vector ipfix_stats = get_ipfix_stats(); // Append IPFIX stats system_counter.insert(system_counter.end(), ipfix_stats.begin(), ipfix_stats.end()); // Common system_counter.push_back(system_counter_t("netflow_ipfix_total_packets", netflow_ipfix_total_packets, metric_type_t::counter, netflow_ipfix_total_packets_desc)); system_counter.push_back(system_counter_t("netflow_ipfix_total_ipv4_packets", netflow_ipfix_total_ipv4_packets, metric_type_t::counter, netflow_ipfix_total_ipv4_packets_desc)); system_counter.push_back(system_counter_t("netflow_ipfix_total_ipv6_packets", netflow_ipfix_total_ipv6_packets, metric_type_t::counter, netflow_ipfix_total_ipv6_packets_desc)); system_counter.push_back(system_counter_t("netflow_ipfix_all_protocols_total_flows", netflow_ipfix_all_protocols_total_flows, metric_type_t::counter, netflow_ipfix_all_protocols_total_flows_desc)); system_counter.push_back(system_counter_t("netflow_ipfix_udp_packet_drops", netflow_ipfix_udp_packet_drops, metric_type_t::counter, netflow_ipfix_udp_packet_drops_desc)); system_counter.push_back(system_counter_t("netflow_ipfix_unknown_protocol_version", netflow_ipfix_unknown_protocol_version, metric_type_t::counter, netflow_ipfix_unknown_protocol_version_desc)); system_counter.push_back(system_counter_t("template_update_attempts_with_same_template_data", template_update_attempts_with_same_template_data, metric_type_t::counter, template_update_attempts_with_same_template_data_desc)); system_counter.push_back(system_counter_t("netflow_ignored_long_flows", netflow_ignored_long_flows, metric_type_t::counter, netflow_ignored_long_flows_desc)); system_counter.push_back(system_counter_t("template_netflow_ipfix_disk_writes", template_netflow_ipfix_disk_writes, metric_type_t::counter, template_netflow_ipfix_disk_writes_desc)); return system_counter; } // Returns fancy name of protocol version std::string get_netflow_protocol_version_as_string(const netflow_protocol_version_t& netflow_protocol_version) { std::string protocol_name = "unknown"; if (netflow_protocol_version == netflow_protocol_version_t::netflow_v9) { protocol_name = "Netflow v9"; } else if (netflow_protocol_version == netflow_protocol_version_t::ipfix) { protocol_name = "IPFIX"; } return protocol_name; } /* Prototypes */ void add_update_peer_template(const netflow_protocol_version_t& netflow_version, std::map>& table_for_add, std::mutex& table_for_add_mutex, uint32_t source_id, uint32_t template_id, const std::string& client_addres_in_string_format, const template_t& field_template, bool& updated, bool& updated_existing_template); int nf9_rec_to_flow(uint32_t record_type, uint32_t record_length, uint8_t* data, simple_packet_t& packet, std::vector& template_records, netflow_meta_info_t& flow_meta); const template_t* peer_find_template(const std::map>& table_for_lookup, std::mutex& table_for_lookup_mutex, uint32_t source_id, uint32_t template_id, const std::string& client_addres_in_string_format) { // We use source_id for distinguish multiple netflow agents with same IP std::string key = client_addres_in_string_format + "_" + std::to_string(source_id); std::lock_guard lock(table_for_lookup_mutex); auto itr = table_for_lookup.find(key); if (itr == table_for_lookup.end()) { return NULL; } // We found entry for specific agent instance and we need to find specific template in it auto itr_template_id = itr->second.find(template_id); // We have no such template if (itr_template_id == itr->second.end()) { return NULL; } // Return pointer to element return &itr_template_id->second; } // Overrides some fields from specified nested packet void override_packet_fields_from_nested_packet(simple_packet_t& packet, const simple_packet_t& nested_packet) { // Copy IP addresses packet.src_ip = nested_packet.src_ip; packet.dst_ip = nested_packet.dst_ip; packet.src_ipv6 = nested_packet.src_ipv6; packet.dst_ipv6 = nested_packet.dst_ipv6; packet.ip_protocol_version = nested_packet.ip_protocol_version; packet.ttl = nested_packet.ttl; // Ports packet.source_port = nested_packet.source_port; packet.destination_port = nested_packet.destination_port; packet.protocol = nested_packet.protocol; packet.length = nested_packet.length; packet.ip_length = nested_packet.ip_length; packet.number_of_packets = 1; packet.flags = nested_packet.flags; packet.ip_fragmented = nested_packet.ip_fragmented; packet.ip_dont_fragment = nested_packet.ip_dont_fragment; packet.vlan = nested_packet.vlan; // Copy Ethernet MAC addresses to main packet structure using native C++ approach to avoid touching memory with memcpy std::copy(std::begin(nested_packet.source_mac), std::end(nested_packet.source_mac), std::begin(packet.source_mac)); std::copy(std::begin(nested_packet.destination_mac), std::end(nested_packet.destination_mac), std::begin(packet.destination_mac)); } void add_update_peer_template( const netflow_protocol_version_t& netflow_protocol_version, std::map>& table_for_add, std::mutex& table_for_add_mutex, uint32_t source_id, uint32_t template_id, const std::string& client_address_in_string_format, const template_t& field_template, bool& updated, bool& updated_existing_template) { std::string key = client_address_in_string_format + "_" + std::to_string(source_id); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Received " << get_netflow_protocol_version_as_string(netflow_protocol_version) << " template with id " << template_id << " from host " << client_address_in_string_format << " source id: " << source_id; } // We need to put lock on it std::lock_guard lock(table_for_add_mutex); auto itr = table_for_add.find(key); if (itr == table_for_add.end()) { std::map temp_template_storage; temp_template_storage[template_id] = field_template; table_for_add[key] = temp_template_storage; updated = true; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "We had no " << get_netflow_protocol_version_as_string(netflow_protocol_version) << " templates for source " << key; logger << log4cpp::Priority::DEBUG << "Added " << get_netflow_protocol_version_as_string(netflow_protocol_version) << " template with ID " << template_id << " for " << key; } return; } // We have information about this agent // Try to find actual template id here if (itr->second.count(template_id) == 0) { if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "We had no information about " << get_netflow_protocol_version_as_string(netflow_protocol_version) << " template with ID " << template_id << " for " << key; logger << log4cpp::Priority::DEBUG << "Added " << get_netflow_protocol_version_as_string(netflow_protocol_version) << " template with ID " << template_id << " for " << key; } itr->second[template_id] = field_template; updated = true; return; } // TODO: Should I track timestamp here and drop old templates after some time? if (itr->second[template_id] != field_template) { // // We can see that template definition actually changed // // In case of IPFIX this is clear protocol violation: // https://datatracker.ietf.org/doc/html/rfc7011#section-8.1 // // // If a Collecting Process receives a new Template Record or Options // Template Record for an already-allocated Template ID, and that // Template or Options Template is different from the already-received // Template or Options Template, this indicates a malfunctioning or // improperly implemented Exporting Process. The continued receipt and // unambiguous interpretation of Data Records for this Template ID are // no longer possible, and the Collecting Process SHOULD log the error. // Further Collecting Process actions are out of scope for this // specification. // // // We cannot follow RFC recommendation for IPFIX as it will break our on disk template caching. // I.e. we may have template with specific list of fields in cache // Then after firmware upgrade vendor changes list of fields but does not change template id // We have to accept new one and update to be able to decode data // // // Netflow v9 explicitly prohibits template content updates: https://www.ietf.org/rfc/rfc3954.txt // // A newly created Template record is assigned an unused Template ID // from the Exporter. If the template configuration is changed, the // current Template ID is abandoned and SHOULD NOT be reused until the // NetFlow process or Exporter restarts. // // // // But in same time Netflow v9 RFC allows template update for collector and that's exactly what we do: // // If a Collector should receive a new definition for an already existing Template ID, it MUST discard // the previous template definition and use the new one. // // On debug level we have to print templates if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Old " << get_netflow_protocol_version_as_string(netflow_protocol_version) <<" template: " << print_template(itr->second[template_id]); logger << log4cpp::Priority::DEBUG << "New " << get_netflow_protocol_version_as_string(netflow_protocol_version) <<" template: " << print_template(field_template); } // We use ERROR level as this behavior is definitely not a common and must be carefully investigated logger << log4cpp::Priority::ERROR << get_netflow_protocol_version_as_string(netflow_protocol_version) << " template " << template_id << " was updated for " << key; // Warn user that something bad going on logger << log4cpp::Priority::ERROR << get_netflow_protocol_version_as_string(netflow_protocol_version) << " template update may be sign of RFC violation by vendor and if you observe this behaviour please reach pavel.odintsov@gmail.com and share information about your equipment and firmware versions"; itr->second[template_id] = field_template; // We need to track this case as it's pretty unusual and in some cases it may be very destructive when router does it incorrectly updated_existing_template = true; updated = true; } else { template_update_attempts_with_same_template_data++; } return; } /* Copy an int (possibly shorter than the target) keeping their LSBs aligned */ #define BE_COPY(a) memcpy((u_char*)&a + (sizeof(a) - record_length), data, record_length); // Safe version of BE_COPY macro bool be_copy_function(const uint8_t* data, uint8_t* target, uint32_t target_field_length, uint32_t record_field_length) { if (target_field_length < record_field_length) { return false; } memcpy(target + (target_field_length - record_field_length), data, record_field_length); return true; } // Updates flow timeouts from device void update_device_flow_timeouts(const device_timeouts_t& device_timeouts, std::mutex& structure_mutex, std::map& timeout_storage, const std::string& client_addres_in_string_format, const netflow_protocol_version_t& netflow_protocol_version) { // We did not receive any information about timeouts // We do not expect that devices reports only active or any inactive timeouts as it does not make any sense if (!device_timeouts.active_timeout.has_value() && !device_timeouts.inactive_timeout.has_value()) { return; } std::lock_guard lock(structure_mutex); auto current_timeouts = timeout_storage.find(client_addres_in_string_format); if (current_timeouts == timeout_storage.end()) { timeout_storage[client_addres_in_string_format] = device_timeouts; logger << log4cpp::Priority::INFO << "Learnt new active flow timeout value: " << device_timeouts.active_timeout.value_or(0) << " seconds " << "and inactive flow timeout value: " << device_timeouts.inactive_timeout.value_or(0) << " seconds for device " << client_addres_in_string_format << " protocol " << get_netflow_protocol_version_as_string(netflow_protocol_version); return; } auto old_flow_timeouts = current_timeouts->second; // They're equal with previously received, nothing to worry about if (old_flow_timeouts == device_timeouts) { return; } // We had values previously logger << log4cpp::Priority::INFO << "Update old active flow timeout value " << current_timeouts->second.active_timeout.value_or(0) << " to " << device_timeouts.active_timeout.value_or(0) << " for " << client_addres_in_string_format << " protocol " << get_netflow_protocol_version_as_string(netflow_protocol_version); logger << log4cpp::Priority::INFO << "Update old inactive flow timeout value " << current_timeouts->second.inactive_timeout.value_or(0) << " to " << device_timeouts.inactive_timeout.value_or(0) << " for " << client_addres_in_string_format << " protocol " << get_netflow_protocol_version_as_string(netflow_protocol_version); current_timeouts->second = device_timeouts; return; } bool process_netflow_packet(uint8_t* packet, uint32_t len, std::string& client_addres_in_string_format, uint32_t client_ipv4_address) { netflow_header_common_t* hdr = (netflow_header_common_t*)packet; switch (ntohs(hdr->version)) { case 5: return process_netflow_packet_v5(packet, len, client_addres_in_string_format, client_ipv4_address); case 9: return process_netflow_packet_v9(packet, len, client_addres_in_string_format, client_ipv4_address); case 10: netflow_ipfix_total_packets++; return process_ipfix_packet(packet, len, client_addres_in_string_format, client_ipv4_address); default: netflow_ipfix_unknown_protocol_version++; logger << log4cpp::Priority::ERROR << "We do not support Netflow " << ntohs(hdr->version) << " we received this packet from " << client_addres_in_string_format; return false; } return true; } void start_netflow_collector(std::string netflow_host, unsigned int netflow_port, bool reuse_port); void start_netflow_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "netflow plugin started"; netflow_process_func_ptr = func_ptr; boost::thread_group netflow_collector_threads; logger << log4cpp::Priority::INFO << "Netflow plugin will listen on " << fastnetmon_global_configuration.netflow_ports.size() << " ports"; for (const auto& netflow_port : fastnetmon_global_configuration.netflow_ports) { bool reuse_port = false; auto netflow_processing_thread = new boost::thread(start_netflow_collector, fastnetmon_global_configuration.netflow_host, netflow_port, reuse_port); // Set unique name std::string thread_name = "netflow_" + std::to_string(netflow_port); set_boost_process_name(netflow_processing_thread, thread_name); netflow_collector_threads.add_thread(netflow_processing_thread); } netflow_collector_threads.join_all(); logger << log4cpp::Priority::INFO << "Function start_netflow_collection was finished"; } void start_netflow_collector(std::string netflow_host, unsigned int netflow_port, bool reuse_port) { logger << log4cpp::Priority::INFO << "netflow plugin will listen on " << netflow_host << ":" << netflow_port << " udp port"; unsigned int udp_buffer_size = 65536; char udp_buffer[udp_buffer_size]; struct addrinfo hints; memset(&hints, 0, sizeof hints); // Could be AF_INET6 or AF_INET hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; // This flag will generate wildcard IP address if we not specified certain IP // address for // binding hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; struct addrinfo* servinfo = NULL; const char* address_for_binding = NULL; if (!netflow_host.empty()) { address_for_binding = netflow_host.c_str(); } char port_as_string[16]; sprintf(port_as_string, "%d", netflow_port); int getaddrinfo_result = getaddrinfo(address_for_binding, port_as_string, &hints, &servinfo); if (getaddrinfo_result != 0) { logger << log4cpp::Priority::ERROR << "Netflow getaddrinfo function failed with code: " << getaddrinfo_result << " please check netflow_host"; return; } int sockfd = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol); if (reuse_port) { // Windows does not support this setsockopt but they may add such logic in future. // Instead of disabling this logic I prefer to define missing constant to address compilation failure #ifdef _WIN32 #define SO_REUSEPORT 15 #endif int reuse_port_optval = 1; // Windows uses char* as 4rd argument: https://learn.microsoft.com/en-gb/windows/win32/api/winsock/nf-winsock-getsockopt and we need to add explicit cast // Linux uses void* https://linux.die.net/man/2/setsockopt // So I think char* works for both platforms auto set_reuse_port_res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (char*)&reuse_port_optval, sizeof(reuse_port_optval)); if (set_reuse_port_res != 0) { logger << log4cpp::Priority::ERROR << "Cannot enable reuse port mode"; return; } } int bind_result = bind(sockfd, servinfo->ai_addr, servinfo->ai_addrlen); if (bind_result) { logger << log4cpp::Priority::ERROR << "Can't listen on port: " << netflow_port << " on host " << netflow_host << " errno:" << errno << " error: " << strerror(errno); return; } struct sockaddr_in6 peer; memset(&peer, 0, sizeof(peer)); /* We should specify timeout there for correct toolkit shutdown */ /* Because otherwise recvfrom will stay in blocked mode forever */ struct timeval tv; tv.tv_sec = 1; /* X Secs Timeout */ tv.tv_usec = 0; // Not init'ing this can cause strange errors setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(struct timeval)); while (true) { // This approach provide ability to store both IPv4 and IPv6 client's // addresses struct sockaddr_storage client_address; // It's MUST memset(&client_address, 0, sizeof(struct sockaddr_storage)); socklen_t address_len = sizeof(struct sockaddr_storage); int received_bytes = recvfrom(sockfd, udp_buffer, udp_buffer_size, 0, (struct sockaddr*)&client_address, &address_len); // logger << log4cpp::Priority::ERROR << "Received " << received_bytes << " with netflow UDP server"; if (received_bytes > 0) { uint32_t client_ipv4_address = 0; if (client_address.ss_family == AF_INET) { // Convert to IPv4 structure struct sockaddr_in* sockaddr_in_ptr = (struct sockaddr_in*)&client_address; client_ipv4_address = sockaddr_in_ptr->sin_addr.s_addr; // logger << log4cpp::Priority::ERROR << "client ip: " << convert_ip_as_uint_to_string(client_ip_address); } else if (client_address.ss_family == AF_INET6) { // We do not support them now } else { // Should not happen } // Pass host and port as numbers without any conversion int getnameinfo_flags = NI_NUMERICSERV | NI_NUMERICHOST; char host[NI_MAXHOST]; char service[NI_MAXSERV]; // TODO: we should check return value here int result = getnameinfo((struct sockaddr*)&client_address, address_len, host, NI_MAXHOST, service, NI_MAXSERV, getnameinfo_flags); // We sill store client's IP address as string for allowing IPv4 and IPv6 // processing in same time std::string client_addres_in_string_format = std::string(host); // logger<< log4cpp::Priority::INFO<<"We receive packet from IP: // "< get_netflow_stats(); pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_meta_info.hpp000066400000000000000000000044661520703010000273070ustar00rootroot00000000000000#pragma once // Variable encoding may be single or two byte and we need to distinguish them explicitly enum class variable_length_encoding_t { unknown, single_byte, two_byte }; // This class carries information which does not need to stay in simple_packet_t because we need it only for parsing class netflow_meta_info_t { public: // Packets selected by sampler uint64_t selected_packets = 0; // Total number of packets on interface uint64_t observed_packets = 0; // Sampling rate is observed_packets / selected_packets // Full length of packet (Netflow Lite) uint64_t data_link_frame_size = 0; // Decoded nested packet simple_packet_t nested_packet; // Set to true when we were able to parse nested packet bool nested_packet_parsed = false; // The IPv4 address of the next IPv4 hop. uint32_t ip_next_hop_ipv4 = 0; // We set this flag when we read it from flow. We need it to distinguish one case when we receive 0.0.0.0 from // device. It's impossible without explicit flag because default value is already 0 bool ip_next_hop_ipv4_set = false; // The IPv4 address of the next (adjacent) BGP hop. uint32_t bgp_next_hop_ipv4 = 0; // We set this flag when we read it from flow. We need it to distinguish one case when we receive 0.0.0.0 from // device. It's impossible without explicit flag because default value is already 0 bool bgp_next_hop_ipv4_set = false; // Next hop flag for IPv6 in6_addr bgp_next_hop_ipv6{}; // Same as in case of IPv4 bool bgp_next_hop_ipv6_set = false; // This flag is set when we explicitly received forwarding status bool received_forwarding_status = false; // Cisco ASA uses very unusual encoding when they encode incoming and outgoing traffic in single flow uint64_t bytes_from_source_to_destination = 0; uint64_t bytes_from_destination_to_source = 0; uint64_t packets_from_source_to_destination = 0; uint64_t packets_from_destination_to_source = 0; // Cisco ASA flow identifier uint64_t flow_id = 0; // Represents NAT events bool nat_event = false; variable_length_encoding_t variable_field_length_encoding = variable_length_encoding_t::unknown; // Store variable field length here to avoid repeating parsing uint16_t variable_field_length = 0; }; pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_template.cpp000066400000000000000000000052271520703010000271500ustar00rootroot00000000000000#include "netflow_template.hpp" #include #include "../fast_library.hpp" #include "../ipfix_fields/ipfix_rfc.hpp" extern ipfix_information_database ipfix_db_instance; bool operator==(const template_t& lhs, const template_t& rhs) { // We do not use timestamp field for comparison here as we're interested only in comparing template fields return lhs.template_id == rhs.template_id && lhs.num_records == rhs.num_records && lhs.total_length == rhs.total_length && lhs.option_scope_length == rhs.option_scope_length && lhs.ipfix_variable_length_elements_used == rhs.ipfix_variable_length_elements_used && lhs.type == rhs.type && lhs.records == rhs.records; } bool operator!=(const template_t& lhs, const template_t& rhs) { return !(lhs == rhs); } bool operator==(const template_record_t& lhs, const template_record_t& rhs) { return lhs.record_type == rhs.record_type && lhs.record_length == rhs.record_length && lhs.enterprise_bit == rhs.enterprise_bit && lhs.enterprise_number == rhs.enterprise_number; } bool operator!=(const template_record_t& lhs, const template_record_t& rhs) { return !(lhs == rhs); } std::string get_netflow_template_type_as_string(netflow_template_type_t type) { if (type == netflow_template_type_t::Data) { return std::string("data"); } else if (type == netflow_template_type_t::Options) { return std::string("options"); } else { return std::string("unknown"); } } std::string print_template(const template_t& field_template) { std::stringstream buffer; buffer << "template_id: " << field_template.template_id << "\n" << "type: " << get_netflow_template_type_as_string(field_template.type) << "\n" << "number of records: " << field_template.num_records << "\n" << "total length: " << field_template.total_length << "\n" << "ipfix_variable_length_elements: " << field_template.ipfix_variable_length_elements_used << "\n" << "option_scope_length: " << field_template.option_scope_length << "\n" << "timestamp: " << print_time_t_in_fastnetmon_format(field_template.timestamp) << "\n"; buffer << "Records\n"; for (auto elem : field_template.records) { buffer << "name: " << ipfix_db_instance.get_name_by_id(elem.record_type) << "\n"; buffer << "record_type: " << elem.record_type << "\n"; buffer << "record_length: " << elem.record_length << "\n"; buffer << "enterprise_bit: " << elem.enterprise_bit << "\n"; buffer << "enterprise_number: " << elem.enterprise_number << "\n"; buffer << "\n"; } return buffer.str(); } pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_template.hpp000066400000000000000000000072121520703010000271510ustar00rootroot00000000000000#pragma once #include #include #include #include "../nlohmann/json.hpp" enum class netflow_template_type_t { Unknown, Data, Options }; // A record in a Netflow v9 template aka IPFIX field specifier class template_record_t { public: uint32_t record_type = 0; uint32_t record_length = 0; // Enterprise bit used in IPFIX RFC: // https://datatracker.ietf.org/doc/html/rfc7011#page-17 bool enterprise_bit = false; // Enterprise number used in IPFIX RFC: // https://datatracker.ietf.org/doc/html/rfc7011#page-17 uint32_t enterprise_number = 0; template_record_t(uint32_t record_type, uint32_t record_length) { this->record_type = record_type; this->record_length = record_length; } // We created custom constructor but I still want to have default with no arguments template_record_t() = default; // For boost serialize template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(record_type); ar& BOOST_SERIALIZATION_NVP(record_length); ar& BOOST_SERIALIZATION_NVP(enterprise_bit); ar& BOOST_SERIALIZATION_NVP(enterprise_number); } // Needed for JSON serialisation: https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ NLOHMANN_DEFINE_TYPE_INTRUSIVE(template_record_t, record_type, record_length, enterprise_bit, enterprise_number) }; bool operator==(const template_record_t& lhs, const template_record_t& rhs); bool operator!=(const template_record_t& lhs, const template_record_t& rhs); // Netflow v9 or IPFIX template record // It's not used for wire data decoding. Feel free to add any new fields class template_t { public: uint16_t template_id = 0; uint32_t num_records = 0; // Total length of all standard records and scope section records // IPFIX: please note that we do not include "fake" length of variable length fields // In this case total length gets meaning "minimum data length" and will be useful for padding detection uint32_t total_length = 0; // Only for options templates uint32_t option_scope_length = 0; // Can be set to true when we use Variable-Length Information Element // https://datatracker.ietf.org/doc/html/rfc7011#page-37 // We need this flag as it triggers special processing logic bool ipfix_variable_length_elements_used = false; // When we received this template for very first time time_t timestamp = 0; // Netflow v9 or IPFIX netflow_template_type_t type = netflow_template_type_t::Unknown; std::vector records; // For boost serialize template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(template_id); ar& BOOST_SERIALIZATION_NVP(num_records); ar& BOOST_SERIALIZATION_NVP(total_length); ar& BOOST_SERIALIZATION_NVP(option_scope_length); ar& BOOST_SERIALIZATION_NVP(timestamp); ar& BOOST_SERIALIZATION_NVP(ipfix_variable_length_elements_used); ar& BOOST_SERIALIZATION_NVP(type); ar& BOOST_SERIALIZATION_NVP(records); } NLOHMANN_DEFINE_TYPE_INTRUSIVE(template_t, template_id, num_records, total_length, option_scope_length, ipfix_variable_length_elements_used, timestamp, type, records) }; std::string print_template(const template_t& field_template); bool operator==(const template_t& lhs, const template_t& rhs); bool operator!=(const template_t& lhs, const template_t& rhs); std::string get_netflow_template_type_as_string(netflow_template_type_t type); pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_v5.hpp000066400000000000000000000040141520703010000256650ustar00rootroot00000000000000// Netflow v5 header class __attribute__((__packed__)) netflow5_header_t { public: netflow_header_common_t header; uint32_t uptime_ms = 0; uint32_t time_sec = 0; uint32_t time_nanosec = 0; uint32_t flow_sequence = 0; uint8_t engine_type = 0; uint8_t engine_id = 0; // "First two bits hold the sampling mode; remaining 14 bits hold value of // sampling interval" // according to https://www.plixer.com/support/netflow_v5.html // http://www.cisco.com/c/en/us/td/docs/net_mgmt/netflow_collection_engine/3-6/user/guide/format.html uint16_t sampling_rate = 0; }; // We are using this class for decoding messages from the wire // Please do not add new fields here class __attribute__((__packed__)) netflow5_flow_t { public: // Source IP uint32_t src_ip = 0; // Destination IP uint32_t dest_ip = 0; // IPv4 next hop uint32_t nexthop_ip = 0; // Input interface uint16_t if_index_in = 0; // Output interface uint16_t if_index_out = 0; // Number of packets in flow uint32_t flow_packets = 0; // Number of bytes / octets in flow uint32_t flow_octets = 0; // Flow start time in milliseconds uint32_t flow_start = 0; // Flow end time in milliseconds uint32_t flow_finish = 0; // Source port uint16_t src_port = 0; // Destination port uint16_t dest_port = 0; // Padding uint8_t pad1 = 0; // TCP flags uint8_t tcp_flags = 0; // Protocol number uint8_t protocol = 0; // Type of service uint8_t tos = 0; // Source ASN uint16_t src_as = 0; // Destination ASN uint16_t dest_as = 0; // Source mask length uint8_t src_mask = 0; // Destination mask length uint8_t dst_mask = 0; // Padding uint16_t pad2 = 0; }; static_assert(sizeof(netflow5_flow_t) == 48, "Bad size for netflow5_flow_t"); #define NETFLOW5_MAXFLOWS 30 #define NETFLOW5_PACKET_SIZE(nflows) (sizeof(netflow5_header_t) + ((nflows) * sizeof(netflow5_flow_t))) pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_v5_collector.cpp000066400000000000000000000212121520703010000277250ustar00rootroot00000000000000#include "netflow_v5_collector.hpp" #include #include #include #include "../fast_library.hpp" #include "netflow.hpp" #include "netflow_v5.hpp" #include "netflow_v5_metrics.hpp" #include "../fast_endianless.hpp" #include "../fastnetmon_configuration_scheme.hpp" extern log4cpp::Category& logger; extern process_packet_pointer netflow_process_func_ptr; // Access to inaccurate but fast time extern time_t current_inaccurate_time; extern uint64_t netflow_ipfix_all_protocols_total_flows; // That's kind of histogram emulation void increment_duration_counters_netflow_v5(int64_t duration) { if (duration <= 15) { netflow5_duration_less_15_seconds++; } else if (duration <= 30) { netflow5_duration_less_30_seconds++; } else if (duration <= 60) { netflow5_duration_less_60_seconds++; } else if (duration <= 90) { netflow5_duration_less_90_seconds++; } else if (duration <= 180) { netflow5_duration_less_180_seconds++; } else { netflow5_duration_exceed_180_seconds++; } return; } std::vector get_netflow_v5_stats() { std::vector system_counter; system_counter.push_back(system_counter_t("netflow_v5_total_packets", netflow_v5_total_packets, metric_type_t::counter, netflow_v5_total_packets_desc)); system_counter.push_back(system_counter_t("netflow_v5_total_flows", netflow_v5_total_flows, metric_type_t::counter, netflow_v5_total_flows_desc)); system_counter.push_back(system_counter_t("netflow_v5_duration_less_15_seconds", netflow5_duration_less_15_seconds, metric_type_t::counter, netflow5_duration_less_15_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v5_duration_less_30_seconds", netflow5_duration_less_30_seconds, metric_type_t::counter, netflow5_duration_less_30_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v5_duration_less_60_seconds", netflow5_duration_less_60_seconds, metric_type_t::counter, netflow5_duration_less_60_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v5_duration_less_90_seconds", netflow5_duration_less_90_seconds, metric_type_t::counter, netflow5_duration_less_90_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v5_duration_less_180_seconds", netflow5_duration_less_180_seconds, metric_type_t::counter, netflow5_duration_less_180_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v5_duration_exceed_180_seconds", netflow5_duration_exceed_180_seconds, metric_type_t::counter, netflow5_duration_exceed_180_seconds_desc)); return system_counter; } bool process_netflow_packet_v5(const uint8_t* packet, uint32_t packet_length, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address) { // logger<< log4cpp::Priority::INFO<<"We got Netflow v5 packet!"; netflow_v5_total_packets++; const netflow5_header_t* netflow5_header = (const netflow5_header_t*)packet; if (packet_length < sizeof(*netflow5_header)) { logger << log4cpp::Priority::ERROR << "Short netflow v5 packet " << packet_length; return false; } uint32_t number_of_flows = ntohs(netflow5_header->header.flows); if (number_of_flows == 0 || number_of_flows > NETFLOW5_MAXFLOWS) { logger << log4cpp::Priority::ERROR << "Invalid number of flows in Netflow v5 packet: " << number_of_flows; return false; } uint16_t netflow5_sampling_ratio = fast_ntoh(netflow5_header->sampling_rate); // In first two bits we store sampling type. // We are not interested in it and should zeroify it for getting correct value // of sampling rate clear_bit_value(netflow5_sampling_ratio, 15); clear_bit_value(netflow5_sampling_ratio, 16); // Sampling not enabled on device if (netflow5_sampling_ratio == 0) { netflow5_sampling_ratio = 1; } // Yes, some vendors can mess up with this value and we need option to override it // According to old Cisco's spec 2 bits are reserved for mode what limits us to 14 bits for storing sampling rate // It's enough to carry only <16k of sampling value // For example, Juniper can encode 50 000 sampling rate using two bits from mode which is not documented anywhere // Juniper's docs think that this field is reserved https://www.juniper.net/documentation/en_US/junos/topics/reference/general/flowmonitoring-output-formats-version5-solutions.html for (uint32_t i = 0; i < number_of_flows; i++) { size_t offset = NETFLOW5_PACKET_SIZE(i); const netflow5_flow_t* netflow5_flow = (const netflow5_flow_t*)(packet + offset); // Check packet bounds if (offset + sizeof(netflow5_flow_t) > packet_length) { logger << log4cpp::Priority::ERROR << "Error! You will try to read outside the Netflow v5 packet"; return false; } netflow_ipfix_all_protocols_total_flows++; netflow_v5_total_flows++; // convert netflow to simple packet form simple_packet_t current_packet; current_packet.source = NETFLOW; current_packet.arrival_time = current_inaccurate_time; current_packet.agent_ipv4_address = client_ipv4_address; current_packet.src_ip = netflow5_flow->src_ip; current_packet.dst_ip = netflow5_flow->dest_ip; current_packet.ts.tv_sec = ntohl(netflow5_header->time_sec); current_packet.ts.tv_usec = ntohl(netflow5_header->time_nanosec); current_packet.flags = 0; //-V1048 // If we have ASN information it should not be zero current_packet.src_asn = fast_ntoh(netflow5_flow->src_as); current_packet.dst_asn = fast_ntoh(netflow5_flow->dest_as); // We do not need fast_ntoh here because we already converted these fields before current_packet.input_interface = fast_ntoh(netflow5_flow->if_index_in); current_packet.output_interface = fast_ntoh(netflow5_flow->if_index_out); current_packet.source_port = 0; //-V1048 current_packet.destination_port = 0; //-V1048 // TODO: we should pass data about "flow" structure of this data // It's pretty interesting because according to Cisco's // http://www.cisco.com/c/en/us/td/docs/net_mgmt/netflow_collection_engine/3-6/user/guide/format.html#wp1006186 // In Netflow v5 we have "Total number of Layer 3 bytes in the packets of the flow" // TODO: so for full length we should use flow_octets + 14 bytes per each packet for more reliable bandwidth // detection current_packet.length = fast_ntoh(netflow5_flow->flow_octets); // Netflow carries only information about number of octets including IP headers and IP payload // which is exactly what we need for ip_length field current_packet.ip_length = current_packet.length; current_packet.number_of_packets = fast_ntoh(netflow5_flow->flow_packets); // This interval in milliseconds, convert it to seconds int64_t interval_length = (fast_ntoh(netflow5_flow->flow_finish) - fast_ntoh(netflow5_flow->flow_start)) / 1000; increment_duration_counters_netflow_v5(interval_length); // TODO: use sampling data from packet, disable customization here // Wireshark dump approves this idea current_packet.sample_ratio = netflow5_sampling_ratio; current_packet.source_port = fast_ntoh(netflow5_flow->src_port); current_packet.destination_port = fast_ntoh(netflow5_flow->dest_port); // We do not support IPv6 in Netflow v5 at all current_packet.ip_protocol_version = 4; //-V1048 switch (netflow5_flow->protocol) { case 1: { // ICMP current_packet.protocol = IPPROTO_ICMP; } break; case 6: { // TCP current_packet.protocol = IPPROTO_TCP; // TODO: flags can be in another format! current_packet.flags = netflow5_flow->tcp_flags; } break; case 17: { // UDP current_packet.protocol = IPPROTO_UDP; } break; } // Call processing function for every flow in packet netflow_process_func_ptr(current_packet); } return true; } pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_v5_collector.hpp000066400000000000000000000005471520703010000277420ustar00rootroot00000000000000#pragma once #include "../fastnetmon_types.hpp" bool process_netflow_packet_v5(const uint8_t* packet, uint32_t packet_length, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address); std::vector get_netflow_v5_stats(); pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_v5_metrics.hpp000066400000000000000000000024321520703010000274150ustar00rootroot00000000000000#pragma once std::string netflow_v5_total_packets_desc = "Total number of Netflow v5 UDP packets received"; uint64_t netflow_v5_total_packets = 0; std::string netflow_v5_total_flows_desc = "Total number of Netflow v5 flows (multiple in each packet)"; uint64_t netflow_v5_total_flows = 0; std::string netflow5_duration_less_15_seconds_desc = "Netflow v5 flows with duration less then 15 seconds"; uint64_t netflow5_duration_less_15_seconds = 0; std::string netflow5_duration_less_30_seconds_desc = "Netflow v5 flows with duration less then 30 seconds"; uint64_t netflow5_duration_less_30_seconds = 0; std::string netflow5_duration_less_60_seconds_desc = "Netflow v5 flows with duration less then 60 seconds"; uint64_t netflow5_duration_less_60_seconds = 0; std::string netflow5_duration_less_90_seconds_desc = "Netflow v5 flows with duration less then 90 seconds"; uint64_t netflow5_duration_less_90_seconds = 0; std::string netflow5_duration_less_180_seconds_desc = "Netflow v5 flows with duration less then 180 seconds"; uint64_t netflow5_duration_less_180_seconds = 0; std::string netflow5_duration_exceed_180_seconds_desc = "Netflow v5 flows with duration more then 180 seconds"; uint64_t netflow5_duration_exceed_180_seconds = 0; pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_v9.hpp000066400000000000000000000125341520703010000256770ustar00rootroot00000000000000#pragma once #include "../fast_endianless.hpp" // Netflow v9 #define NETFLOW9_TEMPLATE_FLOWSET_ID 0 #define NETFLOW9_OPTIONS_FLOWSET_ID 1 #define NETFLOW9_MIN_RECORD_FLOWSET_ID 256 #define NETFLOW9_IN_BYTES 1 #define NETFLOW9_IN_PACKETS 2 #define NETFLOW9_IN_PROTOCOL 4 #define NETFLOW9_SRC_TOS 5 #define NETFLOW9_TCP_FLAGS 6 #define NETFLOW9_L4_SRC_PORT 7 #define NETFLOW9_IPV4_SRC_ADDR 8 #define NETFLOW9_SRC_MASK 9 #define NETFLOW9_INPUT_SNMP 10 #define NETFLOW9_L4_DST_PORT 11 #define NETFLOW9_IPV4_DST_ADDR 12 #define NETFLOW9_DST_MASK 13 #define NETFLOW9_OUTPUT_SNMP 14 #define NETFLOW9_IPV4_NEXT_HOP 15 #define NETFLOW9_SRC_AS 16 #define NETFLOW9_DST_AS 17 #define NETFLOW9_BGP_NEXT_HOP_IPV4_ADDRESS 18 #define NETFLOW9_LAST_SWITCHED 21 #define NETFLOW9_FIRST_SWITCHED 22 #define NETFLOW9_IPV6_SRC_ADDR 27 #define NETFLOW9_IPV6_DST_ADDR 28 #define NETFLOW9_IPV6_SRC_MASK 29 #define NETFLOW9_IPV6_DST_MASK 30 // Juniper MX things, // http://www.juniper.net/techpubs/en_US/junos/topics/task/configuration/flow-aggregation-template-id-configuring-version9-ipfix.html #define NETFLOW9_SAMPLING_INTERVAL 34 #define NETFLOW9_ACTIVE_TIMEOUT 36 #define NETFLOW9_INACTIVE_TIMEOUT 37 #define NETFLOW9_ENGINE_TYPE 38 #define NETFLOW9_ENGINE_ID 39 // ASR 1000 and ASR 9000 use it // It can be used for data and options template and length may by different in each case #define NETFLOW9_FLOW_SAMPLER_ID 48 // 1 byte #define NETFLOW9_FLOW_SAMPLER_MODE 49 // 4 byte #define NETFLOW9_FLOW_SAMPLER_RANDOM_INTERVAL 50 #define NETFLOW9_SOURCE_MAC_ADDRESS 56 #define NETFLOW9_IPV6_NEXT_HOP 62 #define NETFLOW9_DESTINATION_MAC_ADDRESS 80 #define NETFLOW9_INTERFACE_DESCRIPTION 83 // Any length #define NETFLOW9_SAMPLER_NAME 84 // It's used by Bison router when Cisco compatibility mode is disabled // Cisco also used them: https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html #define NETFLOW9_PERMANENT_BYTES 85 #define NETFLOW9_PERMANENT_PACKETS 86 #define NETFLOW9_FORWARDING_STATUS 89 // Legacy field. Recommended replacement is NETFLOW9_DATALINK_FRAME_SIZE // Cisco Catalyst 4500 uses this field with field NETFLOW9_LAYER2_PACKET_SECTION_DATA to deliver Netflow v9 lite #define NETFLOW9_LAYER2_PACKET_SECTION_SIZE 103 #define NETFLOW9_LAYER2_PACKET_SECTION_DATA 104 #define NETFLOW9_FLOW_ID 148 // Cisco calls them "timestamp absolute first" and "timestamp absolute last" #define NETFLOW9_START_MILLISECONDS 152 #define NETFLOW9_END_MILLISECONDS 153 // I did not find any examples if Cisco uses it but it's aligned with IPFIX field with same name and number: https://datatracker.ietf.org/doc/html/rfc7270#section-4.12.1 // Bison router uses it to encode NAT Events #define NETFLOW9_NAT_EVENT 230 // These fields have alternative naming initiator and responder and I find such naming just ridiculous and very tricky to understand // This Cisco ASA guide uses more clear way to name them as source and destination: https://www.cisco.com/c/en/us/td/docs/security/asa/special/netflow/asa_netflow.html #define NETFLOW9_BYTES_FROM_SOURCE_TO_DESTINATION 231 #define NETFLOW9_BYTES_FROM_DESTINATION_TO_SOURCE 232 #define NETFLOW9_PACKETS_FROM_SOURCE_TO_DESTINATION 298 #define NETFLOW9_PACKETS_FROM_DESTINATION_TO_SOURCE 299 #define NETFLOW9_DATALINK_FRAME_SIZE 312 #define NETFLOW9_SELECTOR_TOTAL_PACKETS_OBSERVED 318 #define NETFLOW9_SELECTOR_TOTAL_PACKETS_SELECTED 319 class __attribute__((__packed__)) netflow9_header_common_t { public: uint16_t version = 0; uint16_t flowset_number = 0; }; class __attribute__((__packed__)) netflow9_header_t { public: netflow9_header_common_t header; uint32_t uptime_ms = 0; uint32_t time_sec = 0; uint32_t package_sequence = 0; uint32_t source_id = 0; }; class __attribute__((__packed__)) netflow9_flowset_header_common_t { public: uint16_t flowset_id = 0; uint16_t length = 0; }; class __attribute__((__packed__)) netflow9_template_flowset_header_t { public: uint16_t template_id = 0; uint16_t fields_count = 0; }; class __attribute__((__packed__)) netflow9_template_flowset_record_t { public: uint16_t type = 0; uint16_t length = 0; }; class __attribute__((__packed__)) netflow9_data_flowset_header_t { public: netflow9_flowset_header_common_t header; }; // Docs about format http://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html class __attribute__((__packed__)) netflow9_options_header_common_t { public: uint16_t flowset_id = 0; uint16_t length = 0; }; class __attribute__((__packed__)) netflow9_options_header_t { public: uint16_t template_id = 0; uint16_t option_scope_length = 0; uint16_t option_length = 0; std::string print() { std::stringstream buffer; buffer << "template_id: " << fast_ntoh(template_id) << " " << "option_scope_length: " << fast_ntoh(option_scope_length) << " " << "option_length: " << fast_ntoh(option_length); return buffer.str(); } }; // https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html // I think we can use same format for IPFIX: https://datatracker.ietf.org/doc/html/rfc7270#section-4.12 class __attribute__((__packed__)) netflow9_forwarding_status_t { public: uint8_t reason_code : 6, status : 2; }; pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_v9_collector.cpp000066400000000000000000002543401520703010000277430ustar00rootroot00000000000000#include "netflow_v9_collector.hpp" #include #include #include #include "../fast_library.hpp" #include "netflow.hpp" #include "netflow_template.hpp" #include "netflow_meta_info.hpp" #include "netflow_v9.hpp" #include "netflow_v9_metrics.hpp" #include "../simple_packet_parser_ng.hpp" #include "../all_logcpp_libraries.hpp" #include "../fast_endianless.hpp" #include #include #include #include #include "../fastnetmon_configuration_scheme.hpp" extern fastnetmon_configuration_t fastnetmon_global_configuration; // TODO: get rid of such tricks const template_t* peer_find_template(const std::map>& table_for_lookup, std::mutex& table_for_lookup_mutex, uint32_t source_id, uint32_t template_id, const std::string& client_addres_in_string_format); void add_update_peer_template(const netflow_protocol_version_t& netflow_version, std::map>& table_for_add, std::mutex& table_for_add_mutex, uint32_t source_id, uint32_t template_id, const std::string& client_addres_in_string_format, const template_t& field_template, bool& updated, bool& updated_existing_template); void update_device_flow_timeouts(const device_timeouts_t& device_timeouts, std::mutex& structure_mutex, std::map& timeout_storage, const std::string& client_addres_in_string_format, const netflow_protocol_version_t& netflow_protocol_version); void override_packet_fields_from_nested_packet(simple_packet_t& packet, const simple_packet_t& nested_packet); void update_netflow_v9_sampling_rate(uint32_t new_sampling_rate, const std::string& client_addres_in_string_format); // Access to inaccurate but fast time extern time_t current_inaccurate_time; extern log4cpp::Category& logger; extern process_packet_pointer netflow_process_func_ptr; extern uint64_t template_netflow_ipfix_disk_writes; extern uint64_t netflow_ignored_long_flows; extern uint64_t netflow_ipfix_all_protocols_total_flows; extern uint64_t sets_per_packet_maximum_number; // Sampling rates extracted from Netflow std::mutex netflow9_sampling_rates_mutex; std::map netflow9_sampling_rates; std::mutex global_netflow9_templates_mutex; std::map> global_netflow9_templates; // Netflow v9 per device timeouts std::mutex netflow_v9_per_device_flow_timeouts_mutex; std::map netflow_v9_per_device_flow_timeouts; // Return sampling rate for each device which sends data to us std::vector get_netflow_sampling_rates() { std::vector system_counters; // It should be enough in common cases system_counters.reserve(15); { std::lock_guard lock(netflow9_sampling_rates_mutex); // Copy all elements to output for (const auto& elem : netflow9_sampling_rates) { system_counters.push_back(system_counter_t(elem.first, (uint64_t)elem.second, metric_type_t::gauge, "Sampling rate")); } } return system_counters; } std::vector get_netflow_v9_stats() { std::vector system_counter; system_counter.push_back(system_counter_t("netflow_v9_total_packets", netflow_v9_total_packets, metric_type_t::counter, netflow_v9_total_packets_desc)); system_counter.push_back(system_counter_t("netflow_v9_total_flows", netflow_v9_total_flows, metric_type_t::counter, netflow_v9_total_flows_desc)); system_counter.push_back(system_counter_t("netflow_v9_total_ipv4_flows", netflow_v9_total_ipv4_flows, metric_type_t::counter, netflow_v9_total_ipv4_flows_desc)); system_counter.push_back(system_counter_t("netflow_v9_total_ipv6_flows", netflow_v9_total_ipv6_flows, metric_type_t::counter, netflow_v9_total_ipv6_flows_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_0_seconds", netflow9_duration_0_seconds, metric_type_t::counter, netflow9_duration_0_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_1_seconds", netflow9_duration_less_1_seconds, metric_type_t::counter, netflow9_duration_less_1_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_2_seconds", netflow9_duration_less_2_seconds, metric_type_t::counter, netflow9_duration_less_2_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_3_seconds", netflow9_duration_less_3_seconds, metric_type_t::counter, netflow9_duration_less_3_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_5_seconds", netflow9_duration_less_5_seconds, metric_type_t::counter, netflow9_duration_less_5_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_10_seconds", netflow9_duration_less_10_seconds, metric_type_t::counter, netflow9_duration_less_10_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_15_seconds", netflow9_duration_less_15_seconds, metric_type_t::counter, netflow9_duration_less_15_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_30_seconds", netflow9_duration_less_30_seconds, metric_type_t::counter, netflow9_duration_less_30_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_60_seconds", netflow9_duration_less_60_seconds, metric_type_t::counter, netflow9_duration_less_60_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_90_seconds", netflow9_duration_less_90_seconds, metric_type_t::counter, netflow9_duration_less_90_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_less_180_seconds", netflow9_duration_less_180_seconds, metric_type_t::counter, netflow9_duration_less_180_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_duration_exceed_180_seconds", netflow9_duration_exceed_180_seconds, metric_type_t::counter, netflow9_duration_exceed_180_seconds_desc)); system_counter.push_back(system_counter_t("netflow_v9_data_packet_number", netflow9_data_packet_number, metric_type_t::counter, netflow9_data_packet_number_desc)); system_counter.push_back(system_counter_t("netflow_v9_data_templates_number", netflow9_data_templates_number, metric_type_t::counter, netflow9_data_templates_number_desc)); system_counter.push_back(system_counter_t("netflow_v9_options_templates_number", netflow9_options_templates_number, metric_type_t::counter, netflow9_options_templates_number_desc)); system_counter.push_back(system_counter_t("netflow_v9_options_packet_number", netflow9_options_packet_number, metric_type_t::counter, netflow9_options_packet_number_desc)); system_counter.push_back(system_counter_t("netflow_v9_packets_with_unknown_templates", netflow9_packets_with_unknown_templates, metric_type_t::counter, netflow9_packets_with_unknown_templates_desc)); system_counter.push_back(system_counter_t("netflow_v9_custom_sampling_rate_received", netflow9_custom_sampling_rate_received, metric_type_t::counter, netflow9_custom_sampling_rate_received_desc)); system_counter.push_back(system_counter_t("netflow_v9_sampling_rate_changes", netflow9_sampling_rate_changes, metric_type_t::counter, netflow9_sampling_rate_changes_desc)); system_counter.push_back(system_counter_t("netflow_v9_protocol_version_adjustments", netflow9_protocol_version_adjustments, metric_type_t::counter, netflow9_protocol_version_adjustments_desc)); system_counter.push_back(system_counter_t("netflow_v9_template_updates_number_due_to_real_changes", netflow_v9_template_data_updates, metric_type_t::counter, netflow_v9_template_data_updates_desc)); system_counter.push_back(system_counter_t("netflow_v9_too_large_field", netflow_v9_too_large_field, metric_type_t::counter, netflow_v9_too_large_field_desc)); system_counter.push_back(system_counter_t("netflow_v9_lite_headers", netflow_v9_lite_headers, metric_type_t::counter, netflow_v9_lite_headers_desc)); system_counter.push_back(system_counter_t("netflow_v9_forwarding_status", netflow_v9_forwarding_status, metric_type_t::counter, netflow_v9_forwarding_status_desc)); system_counter.push_back(system_counter_t("netflow_v9_nat_events", netflow_v9_nat_events, metric_type_t::counter, netflow_v9_nat_events_desc)); system_counter.push_back(system_counter_t("netflow_v9_lite_header_parser_success", netflow_v9_lite_header_parser_success, metric_type_t::counter, netflow_v9_lite_header_parser_success_desc)); system_counter.push_back(system_counter_t("netflow_v9_lite_header_parser_error", netflow_v9_lite_header_parser_error, metric_type_t::counter, netflow_v9_lite_header_parser_error_desc)); system_counter.push_back(system_counter_t("netflow_v9_broken_packets", netflow_v9_broken_packets, metric_type_t::counter, netflow_v9_broken_packets_desc)); system_counter.push_back(system_counter_t("netflow_v9_active_flow_timeout_received", netflow_v9_active_flow_timeout_received, metric_type_t::counter, netflow_v9_active_flow_timeout_received_desc)); system_counter.push_back(system_counter_t("netflow_v9_inactive_flow_timeout_received", netflow_v9_inactive_flow_timeout_received, metric_type_t::counter, netflow_v9_inactive_flow_timeout_received_desc)); system_counter.push_back(system_counter_t("netflow_v9_marked_zero_next_hop_and_zero_output_as_dropped", netflow_v9_marked_zero_next_hop_and_zero_output_as_dropped, metric_type_t::counter, netflow_v9_marked_zero_next_hop_and_zero_output_as_dropped_desc)); system_counter.push_back(system_counter_t("netflow_v9_flows_with_sampling_encoded_in_data_packet_number", netflow_v9_flows_with_sampling_encoded_in_data_packet_number, metric_type_t::counter, netflow_v9_flows_with_sampling_encoded_in_data_packet_number_desc)); return system_counter; } // This function reads all available options templates // http://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html bool process_netflow_v9_options_template(const uint8_t* pkt, uint16_t flowset_id, uint16_t flowset_length, uint32_t source_id, const std::string& client_addres_in_string_format) { // We already parsed and validated netflow9_flowset_header_common_t header which carries // flowset_id and flowset_length before calling this function // so we can be sure that we have enough space to read it and it has correct flowset_id // Handy for sanity checking const uint8_t* options_flowset_end = pkt + flowset_length; // Read Netflow v9 options header if (sizeof(netflow9_flowset_header_common_t) + sizeof(netflow9_options_header_t) > flowset_length) { logger << log4cpp::Priority::ERROR << "Could not read specific header for Netflow v9 options template. " << " Agent IP: " << client_addres_in_string_format; return false; } const netflow9_options_header_t* netflow_options_header = (const netflow9_options_header_t*)(pkt + sizeof(netflow9_flowset_header_common_t)); uint16_t template_id = fast_ntoh(netflow_options_header->template_id); uint16_t option_scope_length = fast_ntoh(netflow_options_header->option_scope_length); // Validate that option_scope_length is a multiple of the record size to prevent misaligned reads if (option_scope_length % sizeof(netflow9_template_flowset_record_t) != 0) { logger << log4cpp::Priority::ERROR << "Netflow v9 options template has option_scope_length " << option_scope_length << " which is not a multiple of " << sizeof(netflow9_template_flowset_record_t) << " agent IP: " << client_addres_in_string_format; return false; } if (sizeof(netflow9_flowset_header_common_t) + sizeof(netflow9_options_header_t) + option_scope_length > flowset_length) { logger << log4cpp::Priority::ERROR << "Scope field is longer then flowset length" << " agent IP: " << client_addres_in_string_format; return false; } // Here we keep pointer to beginning of scopes zone const uint8_t* scope_zone_address = pkt + sizeof(netflow9_flowset_header_common_t) + sizeof(netflow9_options_header_t); uint32_t scopes_offset = 0; uint32_t scopes_total_size = 0; // Here I should read all available scopes and calculate total size for (; scopes_offset < option_scope_length;) { if (scope_zone_address + scopes_offset + sizeof(netflow9_template_flowset_record_t) > options_flowset_end) { logger << log4cpp::Priority::ERROR << "Could not read scope record for Netflow v9 options template: need more space for scope record header" << " agent IP: " << client_addres_in_string_format; return false; } const netflow9_template_flowset_record_t* scope_record = (const netflow9_template_flowset_record_t*)(scope_zone_address + scopes_offset); uint32_t current_scope_size = fast_ntoh(scope_record->length); scopes_total_size += current_scope_size; scopes_offset += sizeof(*scope_record); } uint32_t offset = 0; uint32_t records_number = 0; std::vector template_records_map; uint32_t total_template_size = 0; // Length of all options remplate records uint16_t option_length = fast_ntoh(netflow_options_header->option_length); // Validate that option_length is a multiple of the record size to prevent misaligned reads if (option_length % sizeof(netflow9_template_flowset_record_t) != 0) { logger << log4cpp::Priority::ERROR << "Netflow v9 options template has option_length " << option_length << " which is not a multiple of " << sizeof(netflow9_template_flowset_record_t) << " agent IP: " << client_addres_in_string_format; return false; } // Handy address for reading options records const uint8_t* records_zone_address = scope_zone_address + option_scope_length; // Ensure that flowset length is long enough to read all records if (records_zone_address + option_length > options_flowset_end) { logger << log4cpp::Priority::ERROR << "Could not read records for Netflow v9 options template: need more space for records" << " agent IP: " << client_addres_in_string_format; return false; } for (; offset < option_length;) { if (records_zone_address + offset + sizeof(netflow9_template_flowset_record_t) > options_flowset_end) { logger << log4cpp::Priority::ERROR << "Could not read record for Netflow v9 options template: need more space for record header" << " agent IP: " << client_addres_in_string_format; return false; } records_number++; const netflow9_template_flowset_record_t* flowset_record = (const netflow9_template_flowset_record_t*)(records_zone_address + offset); uint32_t record_type = fast_ntoh(flowset_record->type); uint32_t record_length = fast_ntoh(flowset_record->length); template_record_t current_record; current_record.record_type = record_type; current_record.record_length = record_length; template_records_map.push_back(current_record); // logger << log4cpp::Priority::ERROR << "Got type " << record_type << " with length " << record_length; offset += sizeof(*flowset_record); total_template_size += record_length; } template_t field_template{}; field_template.template_id = template_id; field_template.records = template_records_map; field_template.num_records = records_number; field_template.total_length = total_template_size + scopes_total_size; field_template.type = netflow_template_type_t::Options; field_template.option_scope_length = scopes_total_size; // Templates with total length which is zero do not make any sense and have to be ignored // We need templates to decode data blob and decoding zero length value is meaningless if (field_template.total_length == 0) { logger << log4cpp::Priority::ERROR << "Received zero length malformed options Netfow v9 template " << template_id << " from " << client_addres_in_string_format; return false; } // We need to know when we received it field_template.timestamp = current_inaccurate_time; // logger << log4cpp::Priority::INFO << "Read options template:" << print_template(field_template); // Add/update template bool updated = false; bool updated_existing_template = false; add_update_peer_template(netflow_protocol_version_t::netflow_v9, global_netflow9_templates, global_netflow9_templates_mutex, source_id, template_id, client_addres_in_string_format, field_template, updated, updated_existing_template); // This code is not perfect from locks perspective as we read global_netflow9_templates without any locks below // NB! Please be careful with changing name of variable as it's part of serialisation protocol if (updated_existing_template) { netflow_v9_template_data_updates++; } return true; } bool process_netflow_v9_template(const uint8_t* pkt, uint16_t flowset_length, uint32_t source_id, const std::string& client_addres_in_string_format, uint16_t flowset_number) { const netflow9_flowset_header_common_t* template_header = (const netflow9_flowset_header_common_t*)pkt; if (flowset_length < sizeof(*template_header)) { logger << log4cpp::Priority::ERROR << "Short Netflow v9 flowset template header " << flowset_length << " bytes agent IP: " << client_addres_in_string_format << " flowset number: " << flowset_number; return false; } if (fast_ntoh(template_header->flowset_id) != NETFLOW9_TEMPLATE_FLOWSET_ID) { logger << log4cpp::Priority::ERROR << "Function process_netflow_v9_template expects only " "NETFLOW9_TEMPLATE_FLOWSET_ID but " "got another id: " << ntohs(template_header->flowset_id) << " agent IP: " << client_addres_in_string_format; return false; } bool template_cache_update_required = false; for (uint32_t offset = sizeof(*template_header); offset < flowset_length;) { const netflow9_template_flowset_header_t* netflow9_template_flowset_header = (const netflow9_template_flowset_header_t*)(pkt + offset); uint32_t template_id = ntohs(netflow9_template_flowset_header->template_id); uint32_t fields_count = ntohs(netflow9_template_flowset_header->fields_count); offset += sizeof(*netflow9_template_flowset_header); // logger<< log4cpp::Priority::INFO<<"Template template_id // is:"< template_records_map; for (uint32_t i = 0; i < fields_count; i++) { if (offset >= flowset_length) { logger << log4cpp::Priority::ERROR << "Short Netflow v9 flowset template. " << " agent IP: " << client_addres_in_string_format << " flowset number: " << flowset_number; return false; } const netflow9_template_flowset_record_t* template_record_ptr = (const netflow9_template_flowset_record_t*)(pkt + offset); uint32_t record_type = ntohs(template_record_ptr->type); uint32_t record_length = ntohs(template_record_ptr->length); template_record_t current_record; current_record.record_type = record_type; current_record.record_length = record_length; template_records_map.push_back(current_record); // logger<< log4cpp::Priority::INFO<<"Learn new template type: // "<type)<<" // length:"<length); offset += sizeof(*template_record_ptr); total_size += record_length; // TODO: introduce netflow9_check_rec_len } // Templates with total length which is zero do not make any sense and have to be ignored // We need templates to decode data blob and decoding zero length value is meaningless if (total_size == 0) { logger << log4cpp::Priority::ERROR << "Received zero length malformed data Netflow v9 template " << template_id << " from " << client_addres_in_string_format; return false; } template_t field_template{}; field_template.template_id = template_id; field_template.num_records = fields_count; field_template.total_length = total_size; field_template.records = template_records_map; field_template.type = netflow_template_type_t::Data; // We need to know when we received it field_template.timestamp = current_inaccurate_time; // Add/update template bool updated = false; bool updated_existing_template = false; add_update_peer_template(netflow_protocol_version_t::netflow_v9, global_netflow9_templates, global_netflow9_templates_mutex, source_id, template_id, client_addres_in_string_format, field_template, updated, updated_existing_template); // If we have any changes for this template, let's flush them to disk if (updated) { template_cache_update_required = true; } if (updated_existing_template) { netflow_v9_template_data_updates++; } } // for (auto elem: global_netflow9_templates) { // logger << log4cpp::Priority::INFO << "Template ident: " << elem.first << " content: " << // print_template(elem.second); //} return true; } // TODO: get rid of it ASAP // Copy an int (possibly shorter than the target) keeping their LSBs aligned #define BE_COPY(a) memcpy((u_char*)&a + (sizeof(a) - record_length), data, record_length); bool netflow9_record_to_flow(uint32_t record_type, uint32_t record_length, const uint8_t* data, simple_packet_t& packet, netflow_meta_info_t& flow_meta, const std::string& client_addres_in_string_format) { // Some devices such as Mikrotik may pass sampling rate in data section uint32_t sampling_rate = 0; switch (record_type) { case NETFLOW9_IN_BYTES: if (record_length > sizeof(packet.length)) { netflow_v9_too_large_field++; // getPriority just returns private field and does not involve any locking / heavy operations // We do this check to avoid overhead related with << processing if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_IN_BYTES"; } } else { BE_COPY(packet.length); // Decode data in network byte order to host byte order packet.length = fast_ntoh(packet.length); // Netflow carries only information about number of octets including IP headers and IP payload // which is exactly what we need for ip_length field packet.ip_length = packet.length; } break; case NETFLOW9_IN_PACKETS: if (record_length > sizeof(packet.number_of_packets)) { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_IN_PACKETS"; } } else { BE_COPY(packet.number_of_packets); // We need to decode it to host byte order packet.number_of_packets = fast_ntoh(packet.number_of_packets); } break; case NETFLOW9_IN_PROTOCOL: if (record_length > sizeof(packet.protocol)) { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_IN_PROTOCOL"; } } else { BE_COPY(packet.protocol); packet.protocol = fast_ntoh(packet.protocol); } break; case NETFLOW9_TCP_FLAGS: if (record_length > sizeof(packet.flags)) { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_TCP_FLAGS"; } } else { BE_COPY(packet.flags); } break; case NETFLOW9_L4_SRC_PORT: if (record_length > sizeof(packet.source_port)) { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_L4_SRC_PORT"; } } else { BE_COPY(packet.source_port); // We should convert port to host byte order packet.source_port = fast_ntoh(packet.source_port); } break; case NETFLOW9_L4_DST_PORT: if (record_length > sizeof(packet.destination_port)) { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_L4_DST_PORT"; } } else { BE_COPY(packet.destination_port); // We should convert port to host byte order packet.destination_port = fast_ntoh(packet.destination_port); } break; case NETFLOW9_IPV4_SRC_ADDR: if (record_length > sizeof(packet.src_ip)) { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_IPV4_SRC_ADDR"; } } else { memcpy(&packet.src_ip, data, record_length); } break; case NETFLOW9_IPV4_DST_ADDR: if (record_length > sizeof(packet.dst_ip)) { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_IPV4_DST_ADDR"; } } else { memcpy(&packet.dst_ip, data, record_length); } break; case NETFLOW9_SRC_AS: // It could be 2 or 4 byte length if (record_length == 4) { uint32_t src_asn = 0; memcpy(&src_asn, data, record_length); src_asn = fast_ntoh(src_asn); packet.src_asn = src_asn; } else if (record_length == 2) { uint16_t src_asn = 0; memcpy(&src_asn, data, record_length); src_asn = fast_ntoh(src_asn); packet.src_asn = src_asn; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_SRC_AS"; } } break; case NETFLOW9_IPV6_SRC_ADDR: if (true) { // It should be 16 bytes only if (record_length == 16) { memcpy(&packet.src_ipv6, data, record_length); // Set protocol version to IPv6 packet.ip_protocol_version = 6; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_IPV6_SRC_ADDR"; } } } break; case NETFLOW9_IPV6_DST_ADDR: if (true) { // It should be 16 bytes only if (record_length == 16) { memcpy(&packet.dst_ipv6, data, record_length); // Set protocol version to IPv6 packet.ip_protocol_version = 6; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_IPV6_DST_ADDR"; } } } break; case NETFLOW9_DST_AS: // It could be 2 or 4 byte length if (record_length == 4) { uint32_t dst_asn = 0; memcpy(&dst_asn, data, record_length); dst_asn = fast_ntoh(dst_asn); packet.dst_asn = dst_asn; } else if (record_length == 2) { uint16_t dst_asn = 0; memcpy(&dst_asn, data, record_length); dst_asn = fast_ntoh(dst_asn); packet.dst_asn = dst_asn; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_DST_AS"; } } break; case NETFLOW9_INPUT_SNMP: // According to Netflow standard this field could have 2 or more bytes // Juniper MX uses 4 byte encoding // Here we support 2 or 4 byte encoding only if (record_length == 4) { uint32_t input_interface = 0; memcpy(&input_interface, data, record_length); input_interface = fast_ntoh(input_interface); packet.input_interface = input_interface; } else if (record_length == 2) { uint16_t input_interface = 0; memcpy(&input_interface, data, record_length); input_interface = fast_ntoh(input_interface); packet.input_interface = input_interface; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_INPUT_SNMP"; } } break; case NETFLOW9_OUTPUT_SNMP: // According to Netflow standard this field could have 2 or more bytes // Juniper MX uses 4 byte encoding // Here we support 2 or 4 byte encoding only if (record_length == 4) { uint32_t output_interface = 0; memcpy(&output_interface, data, record_length); output_interface = fast_ntoh(output_interface); packet.output_interface = output_interface; } else if (record_length == 2) { uint16_t output_interface = 0; memcpy(&output_interface, data, record_length); output_interface = fast_ntoh(output_interface); packet.output_interface = output_interface; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_OUTPUT_SNMP"; } } break; case NETFLOW9_FIRST_SWITCHED: if (record_length == 4) { uint32_t flow_started = 0; memcpy(&flow_started, data, record_length); flow_started = fast_ntoh(flow_started); packet.flow_start = flow_started; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_FIRST_SWITCHED"; } } break; case NETFLOW9_LAST_SWITCHED: if (record_length == 4) { uint32_t flow_finished = 0; memcpy(&flow_finished, data, record_length); flow_finished = fast_ntoh(flow_finished); packet.flow_end = flow_finished; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_LAST_SWITCHED"; } } break; case NETFLOW9_START_MILLISECONDS: if (record_length == 8) { uint64_t flow_started = 0; memcpy(&flow_started, data, record_length); flow_started = fast_ntoh(flow_started); // We cast unsigned to signed and it may cause issues packet.flow_start = flow_started; } else { netflow_v9_too_large_field++; } break; case NETFLOW9_END_MILLISECONDS: if (record_length == 8) { uint64_t flow_finished = 0; memcpy(&flow_finished, data, record_length); flow_finished = fast_ntoh(flow_finished); // We cast unsigned to signed and it may cause issues packet.flow_end = flow_finished; } else { netflow_v9_too_large_field++; } break; case NETFLOW9_FORWARDING_STATUS: // Documented here: https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html // Forwarding status is encoded on 1 byte with the 2 left bits giving the status and the 6 remaining bits giving the reason code. // This field may carry information about fragmentation but we cannot confirm it, ASR 9000 exports most of the traffic with field 64, which means unknown if (record_length == 1) { uint8_t forwarding_status = 0; memcpy(&forwarding_status, data, record_length); const netflow9_forwarding_status_t* forwarding_status_structure = (const netflow9_forwarding_status_t*)&forwarding_status; // Decode numbers into forwarding statuses packet.forwarding_status = forwarding_status_from_integer(forwarding_status_structure->status); flow_meta.received_forwarding_status = true; netflow_v9_forwarding_status++; // logger << log4cpp::Priority::DEBUG << "Forwarding status: " << int(forwarding_status_structure->status) << " reason code: " << int(forwarding_status_structure->reason_code); } else { // It must be exactly one byte netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_FORWARDING_STATUS"; } } break; case NETFLOW9_SELECTOR_TOTAL_PACKETS_OBSERVED: if (record_length == 8) { uint64_t packets_observed = 0; memcpy(&packets_observed, data, record_length); flow_meta.observed_packets = fast_ntoh(packets_observed); } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_SELECTOR_TOTAL_PACKETS_OBSERVED"; } } break; case NETFLOW9_SELECTOR_TOTAL_PACKETS_SELECTED: if (record_length == 8) { uint64_t packets_selected = 0; memcpy(&packets_selected, data, record_length); flow_meta.selected_packets = fast_ntoh(packets_selected); } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_SELECTOR_TOTAL_PACKETS_SELECTED"; } } break; case NETFLOW9_DATALINK_FRAME_SIZE: if (record_length == 2) { uint16_t datalink_frame_size = 0; memcpy(&datalink_frame_size, data, record_length); flow_meta.data_link_frame_size = fast_ntoh(datalink_frame_size); } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_DATALINK_FRAME_SIZE"; } } break; case NETFLOW9_LAYER2_PACKET_SECTION_SIZE: if (record_length == 2) { uint16_t datalink_frame_size = 0; memcpy(&datalink_frame_size, data, record_length); flow_meta.data_link_frame_size = fast_ntoh(datalink_frame_size); } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_LAYER2_PACKET_SECTION_SIZE"; } } break; case NETFLOW9_LAYER2_PACKET_SECTION_DATA: { netflow_v9_lite_headers++; // It's our safe fallback uint64_t full_packet_length = record_length; // Device must provide this information on previous iteration, let's try to get it in case if we've got it: if (flow_meta.data_link_frame_size != 0) { full_packet_length = flow_meta.data_link_frame_size; } parser_options_t parser_options{}; parser_options.unpack_gre = false; parser_options.parse_mpls = false; parser_options.read_packet_length_from_ip_header = true; auto result = parse_raw_packet_to_simple_packet_full((u_char*)(data), full_packet_length, record_length, flow_meta.nested_packet, parser_options); if (result != parser_code_t::success) { // Cannot decode data netflow_v9_lite_header_parser_error++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Cannot parse packet header with error: " << parser_code_to_string(result); } } else { netflow_v9_lite_header_parser_success++; // Successfully decoded data flow_meta.nested_packet_parsed = true; } break; } // There is a similar field NETFLOW9_BGP_NEXT_HOP_IPV4_ADDRESS but with slightly different meaning // https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html case NETFLOW9_IPV4_NEXT_HOP: // Juniper MX uses this field // Juniper uses this specific field (type 15) to report dropped traffic: // https://apps.juniper.net/feature-explorer/feature-info.html?fKey=7679&fn=Enhancements%20to%20inline%20flow%20monitoring if (record_length == 4) { uint32_t ip_next_hop_ipv4 = 0; memcpy(&ip_next_hop_ipv4, data, record_length); flow_meta.ip_next_hop_ipv4_set = true; flow_meta.ip_next_hop_ipv4 = ip_next_hop_ipv4; // std::cout << "Netflow v9 IP next hop: " << convert_ip_as_uint_to_string(bgp_next_hop_ipv4) << std::endl; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_IPV4_NEXT_HOP"; } } break; // There is a similar field NETFLOW9_BGP_NEXT_HOP_IPV4_ADDRESS but with slightly different meaning // https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html case NETFLOW9_BGP_NEXT_HOP_IPV4_ADDRESS: if (record_length == 4) { uint32_t bgp_next_hop_ipv4 = 0; memcpy(&bgp_next_hop_ipv4, data, record_length); flow_meta.bgp_next_hop_ipv4_set = true; flow_meta.bgp_next_hop_ipv4 = bgp_next_hop_ipv4; // std::cout << "Netflow v9 BGP next hop: " << convert_ip_as_uint_to_string(bgp_next_hop_ipv4) << std::endl; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_BGP_NEXT_HOP_IPV4_ADDRESS"; } } break; case NETFLOW9_FLOW_SAMPLER_ID: // NB! This field for options and data templates field may use different field length if (record_length == 1) { uint8_t sampler_id = 0; memcpy(&sampler_id, data, record_length); // logger << log4cpp::Priority::DEBUG << "Got sampler id from data template: " << int(sampler_id); } else if (record_length == 2) { uint16_t sampler_id = 0; memcpy(&sampler_id, data, record_length); sampler_id = fast_ntoh(sampler_id); // logger << log4cpp::Priority::DEBUG << "Got sampler id from data template: " << int(sampler_id); } else if (record_length == 4) { uint32_t sampler_id = 0; memcpy(&sampler_id, data, record_length); sampler_id = fast_ntoh(sampler_id); // logger << log4cpp::Priority::DEBUG << "Got sampler id from data template: " << int(sampler_id); } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_FLOW_SAMPLER_ID data"; } } break; case NETFLOW9_FLOW_ID: if (record_length == 4) { uint32_t flow_id = 0; memcpy(&flow_id, data, record_length); flow_id = fast_ntoh(flow_id); flow_meta.flow_id = flow_id; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_FLOW_ID"; } } break; case NETFLOW9_BYTES_FROM_SOURCE_TO_DESTINATION: if (record_length == 4) { uint32_t bytes_counter = 0; memcpy(&bytes_counter, data, record_length); bytes_counter = fast_ntoh(bytes_counter); flow_meta.bytes_from_source_to_destination = bytes_counter; } else if (record_length == 8) { uint64_t bytes_counter = 0; memcpy(&bytes_counter, data, record_length); bytes_counter = fast_ntoh(bytes_counter); flow_meta.bytes_from_source_to_destination = bytes_counter; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_BYTES_FROM_SOURCE_TO_DESTINATION"; } } break; case NETFLOW9_BYTES_FROM_DESTINATION_TO_SOURCE: if (record_length == 2) { uint16_t bytes_counter = 0; memcpy(&bytes_counter, data, record_length); bytes_counter = fast_ntoh(bytes_counter); flow_meta.bytes_from_destination_to_source = bytes_counter; } else if (record_length == 4) { uint32_t bytes_counter = 0; memcpy(&bytes_counter, data, record_length); bytes_counter = fast_ntoh(bytes_counter); flow_meta.bytes_from_destination_to_source = bytes_counter; } else if (record_length == 8) { uint64_t bytes_counter = 0; memcpy(&bytes_counter, data, record_length); bytes_counter = fast_ntoh(bytes_counter); flow_meta.bytes_from_destination_to_source = bytes_counter; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_BYTES_FROM_DESTINATION_TO_SOURCE"; } } break; case NETFLOW9_PACKETS_FROM_SOURCE_TO_DESTINATION: if (record_length == 4) { uint32_t packets_counter = 0; memcpy(&packets_counter, data, record_length); packets_counter = fast_ntoh(packets_counter); flow_meta.packets_from_source_to_destination = packets_counter; } else if (record_length == 8) { uint64_t packets_counter = 0; memcpy(&packets_counter, data, record_length); packets_counter = fast_ntoh(packets_counter); flow_meta.packets_from_source_to_destination = packets_counter; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_PACKETS_FROM_SOURCE_TO_DESTINATION"; } } break; case NETFLOW9_PACKETS_FROM_DESTINATION_TO_SOURCE: if (record_length == 4) { uint32_t packets_counter = 0; memcpy(&packets_counter, data, record_length); packets_counter = fast_ntoh(packets_counter); flow_meta.packets_from_destination_to_source = packets_counter; } else if (record_length == 8) { uint64_t packets_counter = 0; memcpy(&packets_counter, data, record_length); packets_counter = fast_ntoh(packets_counter); flow_meta.packets_from_destination_to_source = packets_counter; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_PACKETS_FROM_DESTINATION_TO_SOURCE"; } } break; case NETFLOW9_SOURCE_MAC_ADDRESS: if (record_length == 6) { // Copy it directly to packet structure memcpy(&packet.source_mac, data, record_length); } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_SOURCE_MAC_ADDRESS"; } } break; case NETFLOW9_DESTINATION_MAC_ADDRESS: if (record_length == 6) { // Copy it directly to packet structure memcpy(&packet.destination_mac, data, record_length); } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_DESTINATION_MAC_ADDRESS"; } } break; case NETFLOW9_SAMPLING_INTERVAL: // Well, this record type is expected to be only in options templates but Mikrotik in RouterOS v6.49.6 has // another opinion and we have dump which clearly confirms that they send this data in data templates netflow_v9_flows_with_sampling_encoded_in_data_packet_number++; if (record_length == 4) { uint32_t current_sampling_rate = 0; memcpy(¤t_sampling_rate, data, record_length); current_sampling_rate = fast_ntoh(current_sampling_rate); // Pass it to global variable sampling_rate = current_sampling_rate; // As we have it in data section it may overflow logs but that's best we can do and I think we need to have such information if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Got sampling date from data packet: " << current_sampling_rate; } // // That's where Mikrotik quirks start // From Mikrotik routers with no sampling configured we receive 0 in this field which is not perfect but reasonable enough. // // Another issue that we receive values like: 16777216 which is just 1 wrongly encoded in host byte order in their data // Should I mention that in this case router had following setup: packet-sampling=yes sampling-interval=2222 sampling-space=1111 // And I have no idea what is the source of "1" as sampling rate // It's so broken that we agreed to suspend implementation until they fix it // // Fortunately in ROS7.10 it works just fine // } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_SAMPLING_INTERVAL in data packet"; } } break; case NETFLOW9_PERMANENT_PACKETS: // Bison router uses this encoding when Cisco compatibility is not enabled if (record_length == 8) { uint64_t packets_number = 0; memcpy(&packets_number, data, record_length); packet.number_of_packets = fast_ntoh(packets_number); } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for NETFLOW9_PERMANENT_PACKETS: " << record_length; } } break; case NETFLOW9_PERMANENT_BYTES: // Bison router uses this encoding when Cisco compatibility is not enabled if (record_length == 8) { uint64_t bytes_number = 0; memcpy(&bytes_number, data, record_length); packet.length = fast_ntoh(bytes_number); // Netflow v9 carries only information about number of octets including IP headers and IP payload // which is exactly what we need for ip_length field packet.ip_length = packet.length; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Unexpectedly big size for NETFLOW9_PERMANENT_BYTES: " << record_length; } } break; case NETFLOW9_NAT_EVENT: // Documented here: https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html // It is encoded on 1 byte with the 4 left bits giving the event and the 4 remaining bits giving the reason code. if (record_length == 1) { // Codes: https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-nat-event-type uint8_t nat_event_type = 0; memcpy(&nat_event_type, data, record_length); flow_meta.nat_event = true; netflow_v9_nat_events++; // logger << log4cpp::Priority::DEBUG << "NAT event: " << int(nat_event_type); } else { // It must be exactly one byte netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_NAT_EVENT"; } } break; } // We keep this logic under the fence flag because RouterOS v6 sampling implementation is exceptionally broken and // enabling it by default will not make any good if (true) { // TODO: another issue with this logic that we will run it for each flow in packet which may cause additional // overhead during processing It's not significant and we will keep it that way for now update_netflow_v9_sampling_rate(sampling_rate, client_addres_in_string_format); } return true; } // Read options data packet with known template void netflow9_options_flowset_to_store(const uint8_t* pkt, const netflow9_header_t* netflow9_header, const template_t* flow_template, const std::string& client_addres_in_string_format) { // Skip scope fields, I really do not want to parse this informations pkt += flow_template->option_scope_length; // logger << log4cpp::Priority::ERROR << "We have following length for option_scope_length " << // flow_template->option_scope_length; uint32_t sampling_rate = 0; uint32_t offset = 0; // We may have some fun things encoded here // Cisco ASR9000 encodes mapping between interfaces IDs and interface names here // It uses pairs of two types: type 10 (input SNMP) and type 83 (interface description) interface_id_to_name_t interface_id_to_name; device_timeouts_t device_timeouts{}; for (const auto& elem : flow_template->records) { const uint8_t* data_shift = pkt + offset; // Time to extract sampling rate // Cisco ASR1000 if (elem.record_type == NETFLOW9_FLOW_SAMPLER_RANDOM_INTERVAL) { // According to spec it should be 4 bytes: http://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html // but in real world we saw 2 byte encoding for Cisco ASR1000 if (elem.record_length == 2) { uint16_t current_sampling_rate = 0; memcpy(¤t_sampling_rate, data_shift, elem.record_length); // Convert 2 byte representation to little endian byte order current_sampling_rate = fast_ntoh(current_sampling_rate); sampling_rate = current_sampling_rate; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "2 byte encoded NETFLOW9_FLOW_SAMPLER_RANDOM_INTERVAL sampling rate: " << sampling_rate << " from " << client_addres_in_string_format; } } else if (elem.record_length == 4) { uint32_t current_sampling_rate = 0; memcpy(¤t_sampling_rate, data_shift, elem.record_length); // Convert 4 byte representation to little endian byte order current_sampling_rate = fast_ntoh(current_sampling_rate); sampling_rate = current_sampling_rate; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "4 byte encoded NETFLOW9_FLOW_SAMPLER_RANDOM_INTERVAL sampling rate: " << sampling_rate << " from " << client_addres_in_string_format; } } else { netflow_v9_too_large_field++; logger << log4cpp::Priority::ERROR << "Incorrect length for NETFLOW9_FLOW_SAMPLER_RANDOM_INTERVAL: " << elem.record_length; } } else if (elem.record_type == NETFLOW9_SAMPLING_INTERVAL) { // Juniper MX uses this type to encode sampling rate if (elem.record_length == 2) { uint16_t current_sampling_rate = 0; memcpy(¤t_sampling_rate, data_shift, elem.record_length); // Convert 2 byte representation to little endian byte order current_sampling_rate = fast_ntoh(current_sampling_rate); sampling_rate = current_sampling_rate; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "2 byte encoded NETFLOW9_SAMPLING_INTERVAL sampling rate: " << sampling_rate << " from " << client_addres_in_string_format; } } else if (elem.record_length == 4) { uint32_t current_sampling_rate = 0; memcpy(¤t_sampling_rate, data_shift, elem.record_length); // Convert 4 byte representation to little endian byte order current_sampling_rate = fast_ntoh(current_sampling_rate); sampling_rate = current_sampling_rate; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "4 byte encoded NETFLOW9_SAMPLING_INTERVAL sampling rate: " << sampling_rate << " from " << client_addres_in_string_format; } } else { netflow_v9_too_large_field++; logger << log4cpp::Priority::ERROR << "Incorrect length for NETFLOW9_SAMPLING_INTERVAL: " << elem.record_length; } } else if (elem.record_type == NETFLOW9_INTERFACE_DESCRIPTION) { if (elem.record_length > 128) { // Apply reasonable constraints on maximum interface description field netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_INTERFACE_DESCRIPTION"; } } else { // Find actual length of name ignoring zero characters // In Cisco's encoding all empty symbols are zero bytes size_t interface_name_length = strlen((const char*)data_shift); // It's not clear how strings which have same string length as field itself (i.e. X non zero chars in X // length field) will be encoded I assume in that case router may skip zero byte? if (interface_name_length <= elem.record_length) { // Copy data to string using string length calculated previously interface_id_to_name.interface_description = std::string((const char*)data_shift, interface_name_length); } else { // It may mean that we have no null byte which terminates string netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_INTERFACE_DESCRIPTION"; } } } } else if (elem.record_type == NETFLOW9_INPUT_SNMP) { // https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html claims that it may be 2 or more bytes if (elem.record_length == 4) { uint32_t input_interface = 0; memcpy(&input_interface, data_shift, elem.record_length); input_interface = fast_ntoh(input_interface); interface_id_to_name.interface_id = input_interface; } else if (elem.record_length == 2) { uint16_t input_interface = 0; memcpy(&input_interface, data_shift, elem.record_length); input_interface = fast_ntoh(input_interface); interface_id_to_name.interface_id = input_interface; } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_INPUT_SNMP"; } } } else if (elem.record_type == NETFLOW9_ACTIVE_TIMEOUT) { uint16_t active_timeout = 0; // According to Cisco's specification it should be 2 bytes: https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html if (elem.record_length == 2) { memcpy(&active_timeout, data_shift, elem.record_length); active_timeout = fast_ntoh(active_timeout); netflow_v9_active_flow_timeout_received++; device_timeouts.active_timeout = active_timeout; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Got active timeout: " << active_timeout << " seconds"; } } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_ACTIVE_TIMEOUT"; } } } else if (elem.record_type == NETFLOW9_INACTIVE_TIMEOUT) { uint16_t inactive_timeout = 0; // According to Cisco's specification it should be 2 bytes: https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html if (elem.record_length == 2) { memcpy(&inactive_timeout, data_shift, elem.record_length); inactive_timeout = fast_ntoh(inactive_timeout); netflow_v9_inactive_flow_timeout_received++; device_timeouts.inactive_timeout = inactive_timeout; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Got inactive timeout: " << inactive_timeout << " seconds"; } } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_INACTIVE_TIMEOUT"; } } } else if (elem.record_type == NETFLOW9_FLOW_SAMPLER_ID) { if (elem.record_length == 4) { uint32_t sampler_id = 0; memcpy(&sampler_id, data_shift, elem.record_length); sampler_id = fast_ntoh(sampler_id); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Got sampler id from options template: " << int(sampler_id); } } else if (elem.record_length == 2) { uint16_t sampler_id = 0; memcpy(&sampler_id, data_shift, elem.record_length); sampler_id = fast_ntoh(sampler_id); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Got sampler id from options template: " << int(sampler_id); } } else { netflow_v9_too_large_field++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "Too large field for NETFLOW9_FLOW_SAMPLER_ID options: " << elem.record_length; } } } offset += elem.record_length; } // We print only non zero numbers to distinguish cases when we did not receive anything if (logger.getPriority() == log4cpp::Priority::DEBUG && interface_id_to_name.interface_id != 0) { logger << log4cpp::Priority::DEBUG << "Interface number: " << interface_id_to_name.interface_id << " on " << client_addres_in_string_format << " name: '" << interface_id_to_name.interface_description << "'"; } update_netflow_v9_sampling_rate(sampling_rate, client_addres_in_string_format); // Update flow timeouts in our store update_device_flow_timeouts(device_timeouts, netflow_v9_per_device_flow_timeouts_mutex, netflow_v9_per_device_flow_timeouts, client_addres_in_string_format, netflow_protocol_version_t::netflow_v9); } // Incoming sampling rate uses little endian void update_netflow_v9_sampling_rate(uint32_t new_sampling_rate, const std::string& client_addres_in_string_format) { if (new_sampling_rate == 0) { return; } netflow9_custom_sampling_rate_received++; // logger<< log4cpp::Priority::INFO << "I extracted sampling rate: " << new_sampling_rate // << "for " << client_address_in_string_format; bool any_changes_for_sampling = false; { std::lock_guard lock(netflow9_sampling_rates_mutex); auto known_sampling_rate = netflow9_sampling_rates.find(client_addres_in_string_format); if (known_sampling_rate == netflow9_sampling_rates.end()) { // We had no sampling rates before netflow9_sampling_rates[client_addres_in_string_format] = new_sampling_rate; netflow9_sampling_rate_changes++; logger << log4cpp::Priority::INFO << "Learnt new Netflow v9 sampling rate " << new_sampling_rate << " for " << client_addres_in_string_format; any_changes_for_sampling = true; } else { auto old_sampling_rate = known_sampling_rate->second; if (old_sampling_rate != new_sampling_rate) { netflow9_sampling_rates[client_addres_in_string_format] = new_sampling_rate; netflow9_sampling_rate_changes++; logger << log4cpp::Priority::INFO << "Detected sampling rate change from " << old_sampling_rate << " to " << new_sampling_rate << " for " << client_addres_in_string_format; any_changes_for_sampling = true; } } } } // That's kind of histogram emulation void increment_duration_counters_netflow_v9(int64_t duration) { if (duration == 0) { netflow9_duration_0_seconds++; } else if (duration <= 1) { netflow9_duration_less_1_seconds++; } else if (duration <= 2) { netflow9_duration_less_2_seconds++; } else if (duration <= 3) { netflow9_duration_less_3_seconds++; } else if (duration <= 5) { netflow9_duration_less_5_seconds++; } else if (duration <= 10) { netflow9_duration_less_10_seconds++; } else if (duration <= 15) { netflow9_duration_less_15_seconds++; } else if (duration <= 30) { netflow9_duration_less_30_seconds++; } else if (duration <= 60) { netflow9_duration_less_60_seconds++; } else if (duration <= 90) { netflow9_duration_less_90_seconds++; } else if (duration <= 180) { netflow9_duration_less_180_seconds++; } else { netflow9_duration_exceed_180_seconds++; } return; } void netflow9_flowset_to_store(const uint8_t* pkt, const netflow9_header_t* netflow9_header, const std::vector& template_records, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address) { // Should be done according to // https://github.com/pavel-odintsov/fastnetmon/issues/147 // if (template->total_length > len) // return 1; simple_packet_t packet; packet.source = NETFLOW; packet.arrival_time = current_inaccurate_time; packet.agent_ipv4_address = client_ipv4_address; // We use shifted values and should process only zeroed values // because we are working with little and big endian data in same time packet.number_of_packets = 0; packet.ts.tv_sec = ntohl(netflow9_header->time_sec); // By default, assume IPv4 traffic here // But code below can switch it to IPv6 packet.ip_protocol_version = 4; //-V1048 { std::lock_guard lock(netflow9_sampling_rates_mutex); auto itr = netflow9_sampling_rates.find(client_addres_in_string_format); if (itr == netflow9_sampling_rates.end()) { // Use global value packet.sample_ratio = fastnetmon_global_configuration.netflow_sampling_ratio; } else { packet.sample_ratio = itr->second; } } // Place to keep meta information which is not needed in simple_simple_packet_t structure netflow_meta_info_t flow_meta; uint32_t offset = 0; // We should iterate over all available template fields for (auto iter = template_records.begin(); iter != template_records.end(); iter++) { uint32_t record_type = iter->record_type; uint32_t record_length = iter->record_length; bool netflow9_record_to_flow_result = netflow9_record_to_flow(record_type, record_length, pkt + offset, packet, flow_meta, client_addres_in_string_format); // logger<< log4cpp::Priority::INFO<<"Read data with type: "< :0000:61444 protocol: tcp flags: psh,ack frag: 0 packets: 1 size: 205 bytes ip size: 205 bytes ttl: // 0 sample ratio: 1000 It happens when router sends IPv4 and zero IPv6 fields in same packet if (packet.ip_protocol_version == 6 && is_zero_ipv6_address(packet.src_ipv6) && is_zero_ipv6_address(packet.dst_ipv6) && packet.src_ip != 0 && packet.dst_ip != 0) { netflow9_protocol_version_adjustments++; packet.ip_protocol_version = 4; } if (packet.ip_protocol_version == 4) { netflow_v9_total_ipv4_flows++; } else if (packet.ip_protocol_version == 6) { netflow_v9_total_ipv6_flows++; } double duration_float = packet.flow_end - packet.flow_start; // Covert milliseconds to seconds duration_float = duration_float / 1000; int64_t duration = int64_t(duration_float); // Increments duration counters increment_duration_counters_netflow_v9(duration); // logger<< log4cpp::Priority::INFO<< "Flow start: " << packet.flow_start << " end: " << packet.flow_end << " duration: " << duration; // Logical sources of this logic are unknown but I'm sure we had reasons to do so if (packet.protocol == IPPROTO_ICMP) { // Explicitly set ports to zeros even if device sent something in these fields packet.source_port = 0; packet.destination_port = 0; } // Logic to handle Cisco ASA Netflow v9. We can identify it by zero values for both packet.length and // packet.number_of_packets fields and presence of meta_info.flow_id if (packet.length == 0 && packet.number_of_packets == 0 && flow_meta.flow_id != 0) { // Very likely we're having deal with Cisco ASA // ASA uses bi-directional flows and we need to generate two simple packets for each single one from Cisco // // Original flow example: // bytes_from_source_to_destination:1687 // bytes_from_destination_to_source:2221 // packets_from_source_to_destination:12 // packets_from_destination_to_source:15 // // Examples of two flows generated by this logic: // 71.105.61.155:55819 > 198.252.166.164:443 protocol: tcp flags: - frag: 0 packets: 12 size: 1687 bytes ip // size: 1687 bytes ttl: 0 sample ratio: 1 198.252.166.164:443 > 71.105.61.155:55819 protocol: tcp flags: - // frag: 0 packets: 15 size: 2221 bytes ip size: 2221 bytes ttl: 0 sample ratio: 1 // packet.length = flow_meta.bytes_from_source_to_destination; packet.ip_length = packet.length; packet.number_of_packets = flow_meta.packets_from_source_to_destination; // As ASA's flows are bi-directional we need to create another flow for opposite direction // Create it using original packet before passing it to traffic core for processing as traffic core may alter it simple_packet_t reverse_packet = packet; // Send first packet for processing netflow_process_func_ptr(packet); // Use another set of length and packet counters reverse_packet.length = flow_meta.bytes_from_destination_to_source; reverse_packet.ip_length = reverse_packet.length; reverse_packet.number_of_packets = flow_meta.packets_from_destination_to_source; // Swap IPv4 IPs std::swap(reverse_packet.src_ip, reverse_packet.dst_ip); // Swap IPv6 IPs std::swap(reverse_packet.src_ipv6, reverse_packet.dst_ipv6); // Swap ports std::swap(reverse_packet.source_port, reverse_packet.destination_port); // Swap interfaces std::swap(reverse_packet.input_interface, reverse_packet.output_interface); // Swap ASNs std::swap(reverse_packet.src_asn, reverse_packet.dst_asn); // Swap countries std::swap(reverse_packet.src_country, reverse_packet.dst_country); // Send it for processing netflow_process_func_ptr(reverse_packet); // Stop processing here as this logic is very special and I do not think that we have dropped support for ASA return; } if (false) { // Check that Netflow v9 flow has information only about dropped traffic if (packet.forwarding_status != forwarding_status_t::dropped) { // Discard everything else return; } } // pass data to FastNetMon netflow_process_func_ptr(packet); } bool process_netflow_v9_data(const uint8_t* pkt, size_t flowset_length, const netflow9_header_t* netflow9_header, uint32_t source_id, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address) { extern uint64_t flows_per_packet_maximum_number; const netflow9_data_flowset_header_t* dath = (const netflow9_data_flowset_header_t*)pkt; // Store packet end, it's useful for sanity checks const uint8_t* packet_end = pkt + flowset_length; if (flowset_length < sizeof(*dath)) { logger << log4cpp::Priority::INFO << "Short Netflow v9 data flowset header"; return false; } uint16_t flowset_id = fast_ntoh(dath->header.flowset_id); // logger<< log4cpp::Priority::INFO<<"We have data with flowset_id: " << flowset_id; // We should find template here const template_t* field_template = peer_find_template(global_netflow9_templates, global_netflow9_templates_mutex, source_id, flowset_id, client_addres_in_string_format); if (field_template == NULL) { netflow9_packets_with_unknown_templates++; if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << "We don't have a Netflow 9 template for flowset_id: " << flowset_id << " client " << client_addres_in_string_format << " source_id: " << source_id << " but it's not an error if this message disappears in 5-10 " "seconds. We need some " "time to learn it"; } return true; } if (field_template->records.empty()) { logger << log4cpp::Priority::ERROR << "Blank records in template"; return false; } // Check that template total length is not zero as we're going to divide by it if (field_template->total_length == 0) { logger << log4cpp::Priority::ERROR << "Zero template length is not valid " << "client " << client_addres_in_string_format << " source_id: " << source_id; return false; } uint32_t offset = sizeof(*dath); uint32_t number_of_flows = (flowset_length - offset) / field_template->total_length; if (number_of_flows == 0 || number_of_flows > flows_per_packet_maximum_number) { logger << log4cpp::Priority::ERROR << "Artificially high number of flows: " << number_of_flows; return false; } if (field_template->type == netflow_template_type_t::Data) { for (uint32_t i = 0; i < number_of_flows; i++) { if (pkt + offset + field_template->total_length > packet_end) { logger << log4cpp::Priority::ERROR << "We tried to read data outside packet end in data flowset"; return false; } // Process whole flowset netflow9_flowset_to_store(pkt + offset, netflow9_header, field_template->records, client_addres_in_string_format, client_ipv4_address); offset += field_template->total_length; } } else if (field_template->type == netflow_template_type_t::Options) { // logger << log4cpp::Priority::INFO << "I have " << num_flowsets << " flowsets here"; // logger << log4cpp::Priority::INFO << "Flowset template total length: " << field_template->total_length; netflow9_options_packet_number++; for (uint32_t i = 0; i < number_of_flows; i++) { if (pkt + offset + field_template->total_length > packet_end) { logger << log4cpp::Priority::ERROR << "We tried to read data outside packet end for options flowset"; return false; } // logger << log4cpp::Priority::INFO << "Process flowset: " << i; netflow9_options_flowset_to_store(pkt + offset, netflow9_header, field_template, client_addres_in_string_format); offset += field_template->total_length; } } return true; } bool process_netflow_packet_v9(const uint8_t* packet, uint32_t packet_length, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address) { // logger<< log4cpp::Priority::INFO<<"We got Netflow v9 packet!"; netflow_v9_total_packets++; const netflow9_header_t* netflow9_header = (const netflow9_header_t*)packet; if (packet_length < sizeof(*netflow9_header)) { logger << log4cpp::Priority::ERROR << "Short Netflow v9 header. " << " Agent IP:" << client_addres_in_string_format; return false; } // Number of flow sets in packet, each flow set may carry multiple flows uint16_t flowset_count_total = fast_ntoh(netflow9_header->header.flowset_number); // Limit reasonable number of flow sets per packet if (flowset_count_total > sets_per_packet_maximum_number) { logger << log4cpp::Priority::ERROR << "We have so many flowsets inside Netflow v9 packet: " << flowset_count_total << " Agent IP:" << client_addres_in_string_format; return false; } uint32_t source_id = ntohl(netflow9_header->source_id); uint32_t offset = sizeof(*netflow9_header); // logger<< log4cpp::Priority::INFO<<"Template source id: "<= packet_length) { logger << log4cpp::Priority::ERROR << "We tried to read from address outside Netflow packet agent IP:" << client_addres_in_string_format << " flowset number: " << flowset_number; return false; } // Check that we have enough space in packet to read flowset header if (offset + sizeof(netflow9_flowset_header_common_t) > packet_length) { logger << log4cpp::Priority::ERROR << "Flowset is too short: we do not have space for flowset header. " << "Netflow v9 packet agent IP:" << client_addres_in_string_format << " flowset number: " << flowset_number << " offset: " << offset << " packet_length: " << packet_length; return false; } // Now we can safely read flowset header const netflow9_flowset_header_common_t* flowset = (const netflow9_flowset_header_common_t*)(packet + offset); uint16_t flowset_id = fast_ntoh(flowset->flowset_id); uint16_t flowset_length = fast_ntoh(flowset->length); // One more check to ensure that we have enough space in packet to read whole flowset if (offset + flowset_length > packet_length) { logger << log4cpp::Priority::ERROR << "We tried to read from address outside Netflow's packet flowset agent IP: " << client_addres_in_string_format << " flowset number: " << flowset_number << " flowset_id: " << flowset_id << " flowset_length: " << flowset_length; return false; } if (flowset_id == NETFLOW9_TEMPLATE_FLOWSET_ID) { netflow9_data_templates_number++; // logger<< log4cpp::Priority::INFO<<"We read template"; if (!process_netflow_v9_template(packet + offset, flowset_length, source_id, client_addres_in_string_format, flowset_number)) { return false; } } else if (flowset_id == NETFLOW9_OPTIONS_FLOWSET_ID) { netflow9_options_templates_number++; if (!process_netflow_v9_options_template(packet + offset, flowset_id, flowset_length, source_id, client_addres_in_string_format)) { return false; } } else { if (flowset_id < NETFLOW9_MIN_RECORD_FLOWSET_ID) { logger << log4cpp::Priority::ERROR << "Received unknown Netflow v9 reserved flowset type " << flowset_id << " agent IP: " << client_addres_in_string_format; // Skip current flowset and try to move forward continue; } netflow9_data_packet_number++; // logger<< log4cpp::Priority::INFO<<"We read data"; if (!process_netflow_v9_data(packet + offset, flowset_length, netflow9_header, source_id, client_addres_in_string_format, client_ipv4_address) != 0) { // logger<< log4cpp::Priority::ERROR<<"Can't process function // process_netflow_v9_data correctly"; netflow_v9_broken_packets++; return false; } } // This logic will stop processing if we've reached end of flow set section before reading all flow sets // It's not reliable to use alone because we may have garbage at the end of packet. That's why we have loop over number of flowset records as main condition. offset += flowset_length; if (offset == packet_length) { // Stop for loop break; } } return true; } pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_v9_collector.hpp000066400000000000000000000006661520703010000277500ustar00rootroot00000000000000#pragma once #include #include "../fastnetmon_types.hpp" bool process_netflow_packet_v9(const uint8_t* packet, uint32_t packet_length, const std::string& client_addres_in_string_format, uint32_t client_ipv4_address); std::vector get_netflow_v9_stats(); std::vector get_netflow_sampling_rates(); pavel-odintsov-fastnetmon-394fbe0/src/netflow_plugin/netflow_v9_metrics.hpp000066400000000000000000000141761520703010000274310ustar00rootroot00000000000000#pragma once std::string netflow_v9_total_packets_desc = "Total number of Netflow v9 UDP packets received"; uint64_t netflow_v9_total_packets = 0; std::string netflow_v9_total_flows_desc = "Total number of Netflow v9 flows (multiple in each packet)"; uint64_t netflow_v9_total_flows = 0; std::string netflow_v9_total_ipv4_flows_desc = "Total number of Netflow v9 IPv4 flows (multiple in each packet)"; uint64_t netflow_v9_total_ipv4_flows = 0; std::string netflow_v9_total_ipv6_flows_desc = "Total number of Netflow v9 IPv6 flows (multiple in each packet)"; uint64_t netflow_v9_total_ipv6_flows = 0; std::string netflow_v9_forwarding_status_desc = "Number of Netflow v9 flows with forwarding status provided"; uint64_t netflow_v9_forwarding_status = 0; std::string netflow_v9_nat_events_desc = "Number of Netflow v9 flows with NAT event provided"; uint64_t netflow_v9_nat_events = 0; std::string netflow_v9_marked_zero_next_hop_and_zero_output_as_dropped_desc = "Netflow v9 flow was marked as dropped from interface and next hop information"; uint64_t netflow_v9_marked_zero_next_hop_and_zero_output_as_dropped = 0; std::string netflow_v9_active_flow_timeout_received_desc = "Total number of received active Netflow v9 flow timeouts"; uint64_t netflow_v9_active_flow_timeout_received = 0; std::string netflow_v9_inactive_flow_timeout_received_desc = "Total number of received inactive Netflow v9 flow timeouts"; uint64_t netflow_v9_inactive_flow_timeout_received = 0; std::string netflow_v9_broken_packets_desc = "Netflow v9 packets we cannot decode"; uint64_t netflow_v9_broken_packets = 0; std::string netflow_v9_template_data_updates_desc = "Count times when template data actually changed for Netflow v9"; uint64_t netflow_v9_template_data_updates = 0; std::string netflow_v9_too_large_field_desc = "We increment these counters when field we use to store particular type " "of Netflow v9 record is smaller than we actually received from device"; uint64_t netflow_v9_too_large_field = 0; std::string netflow_v9_lite_header_parser_error_desc = "Netflow v9 Lite header parser errors"; uint64_t netflow_v9_lite_header_parser_error = 0; std::string netflow_v9_lite_header_parser_success_desc = "Netflow v9 Lite header parser success"; uint64_t netflow_v9_lite_header_parser_success = 0; std::string netflow_v9_lite_headers_desc = "Total number of headers in Netflow v9 lite received"; uint64_t netflow_v9_lite_headers = 0; std::string netflow9_protocol_version_adjustments_desc = "Number of Netflow v9 flows with re-classified protocol version"; uint64_t netflow9_protocol_version_adjustments = 0; std::string netflow9_packets_with_unknown_templates_desc = "Number of dropped Netflow v9 packets due to unknown template in message"; uint64_t netflow9_packets_with_unknown_templates = 0; std::string netflow9_duration_0_seconds_desc = "Netflow v9 flows with duration 0 seconds"; uint64_t netflow9_duration_0_seconds = 0; std::string netflow9_duration_less_1_seconds_desc = "Netflow v9 flows with duration less then 1 seconds"; uint64_t netflow9_duration_less_1_seconds = 0; std::string netflow9_duration_less_2_seconds_desc = "Netflow v9 flows with duration less then 2 seconds"; uint64_t netflow9_duration_less_2_seconds = 0; std::string netflow9_duration_less_3_seconds_desc = "Netflow v9 flows with duration less then 3 seconds"; uint64_t netflow9_duration_less_3_seconds = 0; std::string netflow9_duration_less_5_seconds_desc = "Netflow v9 flows with duration less then 5 seconds"; uint64_t netflow9_duration_less_5_seconds = 0; std::string netflow9_duration_less_10_seconds_desc = "Netflow v9 flows with duration less then 10 seconds"; uint64_t netflow9_duration_less_10_seconds = 0; std::string netflow9_duration_less_15_seconds_desc = "Netflow v9 flows with duration less then 15 seconds"; uint64_t netflow9_duration_less_15_seconds = 0; std::string netflow9_duration_less_30_seconds_desc = "Netflow v9 flows with duration less then 30 seconds"; uint64_t netflow9_duration_less_30_seconds = 0; std::string netflow9_duration_less_60_seconds_desc = "Netflow v9 flows with duration less then 60 seconds"; uint64_t netflow9_duration_less_60_seconds = 0; std::string netflow9_duration_less_90_seconds_desc = "Netflow v9 flows with duration less then 90 seconds"; uint64_t netflow9_duration_less_90_seconds = 0; std::string netflow9_duration_less_180_seconds_desc = "Netflow v9 flows with duration less then 180 seconds"; uint64_t netflow9_duration_less_180_seconds = 0; std::string netflow9_duration_exceed_180_seconds_desc = "Netflow v9 flows with duration more then 180 seconds"; uint64_t netflow9_duration_exceed_180_seconds = 0; std::string netflow9_data_packet_number_desc = "Number of Netflow v9 data packets"; uint64_t netflow9_data_packet_number = 0; std::string netflow9_data_templates_number_desc = "Number of Netflow v9 data template packets"; uint64_t netflow9_data_templates_number = 0; std::string netflow9_options_templates_number_desc = "Number of Netflow v9 options templates packets"; uint64_t netflow9_options_templates_number = 0; std::string netflow9_custom_sampling_rate_received_desc = "Number of times we received sampling rate from Netflow v9 agent"; uint64_t netflow9_custom_sampling_rate_received = 0; std::string netflow9_options_packet_number_desc = "Number of Netflow v9 options data packets"; uint64_t netflow9_options_packet_number = 0; std::string netflow9_sampling_rate_changes_desc = "How much times we changed sampling rate for same agent. As change " "we also count when we received it for the first time"; uint64_t netflow9_sampling_rate_changes = 0; // Only Mikrotik uses this approach and we need to know when it happens std::string netflow_v9_flows_with_sampling_encoded_in_data_packet_number_desc = "Number of Netflow v9 flows with sampling rate encoded in data section"; uint64_t netflow_v9_flows_with_sampling_encoded_in_data_packet_number = 0; pavel-odintsov-fastnetmon-394fbe0/src/netmap_plugin/000077500000000000000000000000001520703010000226735ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/netmap_plugin/netmap_collector.cpp000066400000000000000000000245231520703010000267370ustar00rootroot00000000000000#include "../all_logcpp_libraries.hpp" #include #include #include "../fast_library.hpp" // For support uint32_t, uint16_t #include // For config map operations #include #include #include #include #include #define NETMAP_WITH_LIBS // Disable debug messages from Netmap #define NETMAP_NO_DEBUG #include #include #if defined(__FreeBSD__) // On FreeBSD function pthread_attr_setaffinity_np declared here #include // Also we have different type name for cpu set's store typedef cpuset_t cpu_set_t; #endif #include "../simple_packet_parser_ng.hpp" // For pooling operations #include // For support: IPPROTO_TCP, IPPROTO_ICMP, IPPROTO_UDP #include #include #include #include "netmap_collector.hpp" // By default we read packet size from link layer // But in case of Juniper we could crop first X bytes from packet: // maximum-packet-length 110; // And this option become mandatory if we want correct bps speed in toolkit bool netmap_read_packet_length_from_ip_header = false; uint32_t netmap_sampling_ratio = 1; /* prototypes */ void netmap_thread(struct nm_desc* netmap_descriptor, int netmap_thread); void consume_pkt(u_char* buffer, int len, int thread_number); // Get log4cpp logger from main program extern log4cpp::Category& logger; // Pass unparsed packets number to main program extern uint64_t total_unparsed_packets; // Global configuration map extern std::map configuration_map; u_int num_cpus = 0; // This variable name should be uniq for every plugin! process_packet_pointer netmap_process_func_ptr = NULL; bool execute_strict_cpu_affinity = true; int receive_packets(struct netmap_ring* ring, int thread_number) { u_int cur, rx, n; cur = ring->cur; n = nm_ring_space(ring); for (rx = 0; rx < n; rx++) { struct netmap_slot* slot = &ring->slot[cur]; char* p = NETMAP_BUF(ring, slot->buf_idx); // process data consume_pkt((u_char*)p, slot->len, thread_number); cur = nm_ring_next(ring, cur); } ring->head = ring->cur = cur; return (rx); } void consume_pkt(u_char* buffer, int len, int thread_number) { // We should fill this structure for passing to FastNetMon simple_packet_t packet; packet.sample_ratio = netmap_sampling_ratio; parser_options_t parser_options{}; parser_options.unpack_gre = false; parser_options.read_packet_length_from_ip_header = netmap_read_packet_length_from_ip_header; auto result = parse_raw_packet_to_simple_packet_full((u_char*)buffer, len, len, packet, parser_options); if (result != parser_code_t::success) { total_unparsed_packets++; return; } netmap_process_func_ptr(packet); } void receiver(std::string interface_for_listening) { struct nm_desc* netmap_descriptor; struct nmreq base_nmd; bzero(&base_nmd, sizeof(base_nmd)); // Magic from pkt-gen.c base_nmd.nr_tx_rings = base_nmd.nr_rx_rings = 0; base_nmd.nr_tx_slots = base_nmd.nr_rx_slots = 0; std::string interface = ""; std::string system_interface_name = ""; // If we haven't netmap: prefix in interface name we will append it if (interface_for_listening.find("netmap:") == std::string::npos) { system_interface_name = interface_for_listening; interface = "netmap:" + interface_for_listening; } else { // We should skip netmap prefix system_interface_name = boost::replace_all_copy(interface_for_listening, "netmap:", ""); interface = interface_for_listening; } #ifdef __linux__ manage_interface_promisc_mode(system_interface_name, true); logger.warn("Please disable all types of offload for this NIC manually: ethtool -K %s gro off " "gso off tso off lro off", system_interface_name.c_str()); #endif netmap_descriptor = nm_open(interface.c_str(), &base_nmd, 0, NULL); if (netmap_descriptor == NULL) { logger.error("Can't open netmap device %s", interface.c_str()); return; } logger.info("Mapped %dKB memory at %p", netmap_descriptor->req.nr_memsize >> 10, netmap_descriptor->mem); logger.info("We have %d tx and %d rx rings", netmap_descriptor->req.nr_tx_rings, netmap_descriptor->req.nr_rx_rings); if (num_cpus > netmap_descriptor->req.nr_rx_rings) { num_cpus = netmap_descriptor->req.nr_rx_rings; logger.info("We have number of CPUs bigger than number of NIC RX queues. Set number of " "CPU's to number of threads"); } /* protocol stack and may cause a reset of the card, which in turn may take some time for the PHY to reconfigure. We do the open here to have time to reset. */ int wait_link = 2; logger.info("Wait %d seconds for NIC reset", wait_link); sleep(wait_link); boost::thread_group packet_receiver_thread_group; for (int i = 0; i < num_cpus; i++) { struct nm_desc nmd = *netmap_descriptor; // This operation is VERY important! nmd.self = &nmd; uint64_t nmd_flags = 0; if (nmd.req.nr_flags != NR_REG_ALL_NIC) { logger.error("Ooops, main descriptor should be with NR_REG_ALL_NIC flag"); } nmd.req.nr_flags = NR_REG_ONE_NIC; nmd.req.nr_ringid = i; /* Only touch one of the rings (rx is already ok) */ nmd_flags |= NETMAP_NO_TX_POLL; struct nm_desc* new_nmd = nm_open(interface.c_str(), NULL, nmd_flags | NM_OPEN_IFNAME | NM_OPEN_NO_MMAP, &nmd); if (new_nmd == NULL) { logger.error("Can't open netmap descriptor for netmap per hardware queue thread"); return; } logger.info("My first ring is %d and last ring id is %d I'm thread %d", new_nmd->first_rx_ring, new_nmd->last_rx_ring, i); /* logger<< log4cpp::Priority::INFO<< "We are using Boost " << BOOST_VERSION / 100000 << "." // major version << BOOST_VERSION / 100 % 1000 << "." // minior version << BOOST_VERSION % 100; */ logger.info("Start new netmap thread %d", i); #if defined(BOOST_THREAD_PLATFORM_PTHREAD) && !defined(__APPLE__) /* Bind to certain core */ boost::thread::attributes thread_attrs; if (execute_strict_cpu_affinity) { cpu_set_t current_cpu_set; int cpu_to_bind = i % num_cpus; CPU_ZERO(¤t_cpu_set); // We count cpus from zero CPU_SET(cpu_to_bind, ¤t_cpu_set); logger.info("I will bind this thread to logical CPU: %d", cpu_to_bind); int set_affinity_result = pthread_attr_setaffinity_np(thread_attrs.native_handle(), sizeof(cpu_set_t), ¤t_cpu_set); if (set_affinity_result != 0) { logger.error("Can't specify CPU affinity for netmap thread"); } } // Start thread and pass netmap descriptor to it packet_receiver_thread_group.add_thread(new boost::thread(thread_attrs, boost::bind(netmap_thread, new_nmd, i))); #else logger.error("Sorry but CPU affinity did not supported for your platform"); packet_receiver_thread_group.add_thread(new boost::thread(netmap_thread, new_nmd, i)); #endif } // Wait all threads for completion packet_receiver_thread_group.join_all(); } void netmap_thread(struct nm_desc* netmap_descriptor, int thread_number) { struct nm_pkthdr h; u_char* buf; struct pollfd fds; fds.fd = netmap_descriptor->fd; // NETMAP_FD(netmap_descriptor); fds.events = POLLIN; struct netmap_ring* rxring = NULL; struct netmap_if* nifp = netmap_descriptor->nifp; // printf("Reading from fd %d thread id: %d", netmap_descriptor->fd, thread_number); for (;;) { // We will wait 1000 microseconds for retry, for infinite timeout please use -1 int poll_result = poll(&fds, 1, 1000); if (poll_result == 0) { // printf("poll return 0 return code"); continue; } if (poll_result == -1) { logger.error("Netmap plugin: poll failed with return code -1"); } for (int i = netmap_descriptor->first_rx_ring; i <= netmap_descriptor->last_rx_ring; i++) { // printf("Check ring %d from thread %d", i, thread_number); rxring = NETMAP_RXRING(nifp, i); if (nm_ring_empty(rxring)) { continue; } receive_packets(rxring, thread_number); } // TODO: this code could add performance degradation // Add interruption point for correct toolkit shutdown // boost::this_thread::interruption_point(); } // nm_close(netmap_descriptor); } void start_netmap_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "Netmap plugin started"; netmap_process_func_ptr = func_ptr; num_cpus = sysconf(_SC_NPROCESSORS_ONLN); logger.info("We have %d cpus", num_cpus); std::string interfaces_list = ""; if (configuration_map.count("interfaces") != 0) { interfaces_list = configuration_map["interfaces"]; } if (configuration_map.count("netmap_sampling_ratio") != 0) { netmap_sampling_ratio = convert_string_to_integer(configuration_map["netmap_sampling_ratio"]); } if (configuration_map.count("netmap_read_packet_length_from_ip_header") != 0) { netmap_read_packet_length_from_ip_header = configuration_map["netmap_read_packet_length_from_ip_header"] == "on"; } std::vector interfaces_for_listen; boost::split(interfaces_for_listen, interfaces_list, boost::is_any_of(","), boost::token_compress_on); logger << log4cpp::Priority::INFO << "netmap will listen on " << interfaces_for_listen.size() << " interfaces"; // Thread group for all "master" processes boost::thread_group netmap_main_threads; for (std::vector::iterator interface = interfaces_for_listen.begin(); interface != interfaces_for_listen.end(); ++interface) { logger << log4cpp::Priority::INFO << "netmap will sniff interface: " << *interface; netmap_main_threads.add_thread(new boost::thread(receiver, *interface)); } netmap_main_threads.join_all(); } pavel-odintsov-fastnetmon-394fbe0/src/netmap_plugin/netmap_collector.hpp000066400000000000000000000002341520703010000267350ustar00rootroot00000000000000#ifndef NETMAP_PLUGIN_H #define NETMAP_PLUGIN_H #include "../fastnetmon_types.hpp" void start_netmap_collection(process_packet_pointer func_ptr); #endif pavel-odintsov-fastnetmon-394fbe0/src/network_data_structures.cpp000066400000000000000000000000001520703010000255100ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/network_data_structures.hpp000066400000000000000000001261711520703010000255370ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include "fast_endianless.hpp" #include "iana/iana_ethertypes.hpp" #include "iana/iana_ip_protocols.hpp" // // If you have an idea to add unions to "improve" this code please stop and think. // // More details about issue https://en.cppreference.com/w/cpp/language/union // // "It is undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, // as a non-standard language extension, the ability to read inactive members of a union." // // Few more additional details: https://stackoverflow.com/questions/53074726/c-union-struct-bitfield-implementation-and-portability/53074781#53074781 // // This function could copy X bytes from src to dst. // Where X - size of dst object (referenced by pointer) template inline void* smart_memcpy(dst_type* dst, const src_type* src) { return memcpy(dst, src, sizeof(dst_type)); } // We are using this structure as pretty interface for IPv4 address bytes in host byte order (little endian) class __attribute__((__packed__)) ipv4_octets_form_little_endian_t { public: uint8_t fourth = 0; uint8_t third = 0; uint8_t second = 0; uint8_t first = 0; }; static_assert(sizeof(ipv4_octets_form_little_endian_t) == 4, "Bad size for ipv4_octets_form_little_endian_t"); class __attribute__((__packed__)) ipv4_octets_form_big_endian_t { public: uint8_t first = 0; uint8_t second = 0; uint8_t third = 0; uint8_t fourth = 0; }; static_assert(sizeof(ipv4_octets_form_big_endian_t) == 4, "Bad size for ipv4_octets_form_big_endian_t"); // Convert IP as integer in little endian to string representation inline std::string convert_ip_as_little_endian_to_string(uint32_t ip) { /* Actually we could use inet_ntoa but it's implementation uses not very convenient data structures (struct in_addr) Also it has multi thread issues (because it's using common buffer) and it solved by thread local storage. Which could produce performance issues too (chec http://www.agner.org) Here you could https://github.com/bminor/glibc/blob/0a1f1e78fbdfaf2c01e9c2368023b2533e7136cf/inet/inet_ntoa.c#L31 And has known performance issues: https://github.com/h2o/qrintf I decided to implement it manually */ const size_t max_ip_as_string_size = 16; // Maximum string length as integer char buffer[max_ip_as_string_size]; ipv4_octets_form_little_endian_t* ipv4_octets = (ipv4_octets_form_little_endian_t*)&ip; snprintf(buffer, max_ip_as_string_size, "%d.%d.%d.%d", ipv4_octets->first, ipv4_octets->second, ipv4_octets->third, ipv4_octets->fourth); return std::string(buffer); } // Convert IP as integer in big endian to string representation inline std::string convert_ip_as_big_endian_to_string(uint32_t ip) { /* Actually we could use inet_ntoa but it's implementation uses not very convenient data structures (struct in_addr) Also it has multi thread issues (because it's using common buffer) and it solved by thread local storage. Which could produce performance issues too (chec http://www.agner.org) Here you could https://github.com/bminor/glibc/blob/0a1f1e78fbdfaf2c01e9c2368023b2533e7136cf/inet/inet_ntoa.c#L31 And has known performance issues: https://github.com/h2o/qrintf I decided to implement it manually */ const size_t max_ip_as_string_size = 16; // Maximum string length as integer char buffer[max_ip_as_string_size]; ipv4_octets_form_big_endian_t* ipv4_octets = (ipv4_octets_form_big_endian_t*)&ip; snprintf(buffer, max_ip_as_string_size, "%d.%d.%d.%d", ipv4_octets->first, ipv4_octets->second, ipv4_octets->third, ipv4_octets->fourth); return std::string(buffer); } // Here we are using very cryptic form of pointer to fixed size array inline std::string convert_mac_to_string(const uint8_t (&mac_as_array)[6]) { std::stringstream buffer; for (int i = 0; i < 6; i++) { buffer << std::hex << std::setfill('0') << std::setw(2) << int(mac_as_array[i]); if (i != 5) { buffer << ":"; } } return buffer.str(); } // Well, this one is special, it 4 bytes lkong one and we need to do big endian to little endian conversion before // reading any of these fields We cannot convert each field invidually as for other fields // https://datatracker.ietf.org/doc/html/rfc3032#page-3 class __attribute__((__packed__)) mpls_label_t { public: uint32_t ttl : 8 = 0, bottom_of_stack : 1 = 0, qos : 3 = 0, label : 20 = 0; std::string print() const { std::stringstream buffer; buffer << "label: " << uint32_t(label) << " " << "qos: " << uint32_t(qos) << " " << "bottom of stack: " << uint32_t(bottom_of_stack) << " " << "ttl: " << uint32_t(ttl); return buffer.str(); } }; // We use this structure to guess version of packet after last MPLS tag class __attribute__((__packed__)) ipv4_or_ipv6_header_t { // We must not access these fields directly as they need conversion private: uint8_t ignore : 4 = 0, version : 4 = 0; public: uint8_t get_version() const { return version; } }; static_assert(sizeof(ipv4_or_ipv6_header_t) == 1, "Bad size for ipv4_or_ipv6_header_t"); static_assert(sizeof(mpls_label_t) == 4, "Bad size for mpls_label_t"); // In this class we keep vlan id, priority and cfi class __attribute__((__packed__)) ethernet_vlan_metadata_t { public: uint16_t vlan_id : 12, cfi : 1, priority : 3; }; static_assert(sizeof(ethernet_vlan_metadata_t) == 2, "Bad size for ethernet_vlan_metadata_t"); // We are storing VLAN meta data and next ethertype in same packet // It's not standard approach so be careful class __attribute__((__packed__)) ethernet_vlan_header_t { // We must not access these fields directly as it requires explicit byte order conversion private: uint16_t vlan_metadata_as_integer = 0; uint16_t ethertype = 0; // We can access data in packet only using special methods which can do all required format conversions public: // Returns ethertype in host byte order uint16_t get_ethertype_host_byte_order() const { return fast_ntoh(ethertype); } // Returns VLAN id in host byte order. You must call convert before calling it uint16_t get_vlan_id_host_byte_order() const { // Copy whole structure and convert to host byte order uint16_t vlan_metadata_little_endian = fast_ntoh(vlan_metadata_as_integer); // Apply mask to retrieve vlan id field // TODO: I'm not 100% sure that it will work correct if cfi or priority are non zero or vlan id is relatively large number const ethernet_vlan_metadata_t* vlan_metadata = (ethernet_vlan_metadata_t*)&vlan_metadata_little_endian; return vlan_metadata->vlan_id; } // Returns CFI flag. You must call convert before calling it // TODO: We never tested this logic bool get_cfi_flag() const { // Copy whole structure and convert to host byte order uint16_t vlan_metadata_little_endian = fast_ntoh(vlan_metadata_as_integer); const ethernet_vlan_metadata_t* vlan_metadata = (ethernet_vlan_metadata_t*)&vlan_metadata_little_endian; return vlan_metadata->cfi; } // Return priority in host byte order. You must call convert before calling it // TODO: We never tested this logic uint8_t get_priority() const { // Copy whole structure and convert to host byte order uint16_t vlan_metadata_little_endian = fast_ntoh(vlan_metadata_as_integer); const ethernet_vlan_metadata_t* vlan_metadata = (ethernet_vlan_metadata_t*)&vlan_metadata_little_endian; return vlan_metadata->priority; } std::string print() const { std::stringstream buffer; // We use cast to avoid printing it as char buffer << "priority: " << uint32_t(get_priority()) << " "; buffer << "cfi: " << std::boolalpha << get_cfi_flag() << " " << "vlan_id: " << get_vlan_id_host_byte_order() << " "; buffer << "ethertype: 0x" << std::setfill('0') << std::setw(4) << std::hex << get_ethertype_host_byte_order(); return buffer.str(); } }; static_assert(sizeof(ethernet_vlan_header_t) == 4, "Bad size for ethernet_vlan_header_t"); class __attribute__((__packed__)) ethernet_header_t { public: uint8_t destination_mac[6]; uint8_t source_mac[6]; private: // We must not access this field directly as it requires explicit byte order conversion uint16_t ethertype = 0; public: // Returns ethertype in host byte order uint16_t get_ethertype_host_byte_order() const { return fast_ntoh(ethertype); } std::string print() { std::stringstream buffer; buffer << "ethertype: 0x" << std::setfill('0') << std::setw(4) << std::hex << get_ethertype_host_byte_order(); buffer << " " << "source mac: " << convert_mac_to_string(source_mac) << " " << "destination mac: " << convert_mac_to_string(destination_mac); return buffer.str(); } }; static_assert(sizeof(ethernet_header_t) == 14, "Bad size for ethernet_header_t"); // Please be careful! // This structure will work only for IPv4 (4 byte address) + ethernet (6 byte // address) class __attribute__((__packed__)) arp_header_t { // These fields must not be accessed directly as we need to convert them to host byte order private: uint16_t hardware_type = 0; uint16_t protocol_type = 0; uint8_t hardware_address_length = 0; uint8_t protocol_address_length = 0; uint16_t operation = 0; uint8_t sender_hardware_address[6]; uint32_t sender_protocol_address = 0; uint8_t target_hardware_address[6]; uint32_t target_protocol_address = 0; public: uint8_t get_hardware_address_length() const { return hardware_address_length; } uint8_t get_protocol_address_length() const { return protocol_address_length; } uint16_t get_hardware_type_host_byte_order() const { return fast_ntoh(hardware_type); } uint16_t get_protocol_type_host_byte_order() const { return fast_ntoh(protocol_type); } uint16_t get_operation_host_byte_order() const { return fast_ntoh(operation); } // Return it as is uint32_t get_sender_protocol_address_network_byte_order() const { return sender_protocol_address; } // Return it as is uint32_t get_target_protocol_address_network_byte_order() const { return target_protocol_address; } std::string print() const { std::stringstream buffer; buffer << "hardware_type: " << get_hardware_type_host_byte_order() << " " << "protocol_type: " << get_protocol_type_host_byte_order() << " " << "hardware_address_length: " << uint32_t(get_hardware_address_length()) << " " << "protocol_address_length: " << uint32_t(get_protocol_address_length()) << " " << "operation: " << get_operation_host_byte_order() << " " << "sender_hardware_address: " << convert_mac_to_string(sender_hardware_address) << " " << "sender_protocol_address: " << convert_ip_as_big_endian_to_string(get_sender_protocol_address_network_byte_order()) << " " << "target_hardware_address: " << convert_mac_to_string(target_hardware_address) << " " << "target_protocol_address: " << convert_ip_as_big_endian_to_string(get_target_protocol_address_network_byte_order()); return buffer.str(); } }; static_assert(sizeof(arp_header_t) == 28, "Bad size for arp_header_t"); class __attribute__((__packed__)) icmp_header_t { // We must not access these fields directly as they may need conversion private: uint8_t type = 0; uint8_t code = 0; uint16_t checksum = 0; uint32_t rest_of_header = 0; public: uint8_t get_type() const { return type; } uint8_t get_code() const { return code; } uint16_t get_checksum() const { return fast_ntoh(checksum); } std::string print() const { std::stringstream buffer; buffer << "type: " << uint32_t(get_type()) << " " << "code: " << uint32_t(get_code()) << " " << "checksum: " << uint32_t(get_checksum()); return buffer.str(); } }; class __attribute__((__packed__)) udp_header_t { // We must not access these fields directly as they may need conversion private: uint16_t source_port = 0; uint16_t destination_port = 0; uint16_t length = 0; uint16_t checksum = 0; public: uint16_t get_source_port_host_byte_order() const { return fast_ntoh(source_port); } uint16_t get_destination_port_host_byte_order() const { return fast_ntoh(destination_port); } uint16_t get_length_host_byte_order() const { return fast_ntoh(length); } uint16_t get_checksum_host_byte_order() const { return fast_ntoh(checksum); } std::string print() const { std::stringstream buffer; buffer << "source_port: " << get_source_port_host_byte_order() << " " << "destination_port: " << get_destination_port_host_byte_order() << " " << "length: " << get_length_host_byte_order() << " " << "checksum: " << get_checksum_host_byte_order(); return buffer.str(); } }; static_assert(sizeof(udp_header_t) == 8, "Bad size for udp_header_t"); // Encodes flags class __attribute__((__packed__)) gtp_v1_header_flags_t { public: uint8_t n_pdu_number : 1 = 0, sequence_number : 1 = 0, extension_header : 1 = 0, reserved : 1 = 0, protocol_type : 1 = 0, version : 3 = 0; std::string print() const { std::stringstream buffer; buffer << "version: " << uint32_t(version) << " " << "protocol_type: " << uint32_t(protocol_type) << " " << "extension_header: " << uint32_t(extension_header) << " " << "sequence_number: " << uint32_t(sequence_number) << " " << "n_pdu_number: " << uint32_t(n_pdu_number); return buffer.str(); } }; static_assert(sizeof(gtp_v1_header_flags_t) == 1, "Bad size for gtp_v1_header_flags_t"); /* Source, page: 15, 3GPP TS 29.281 version 10.1.0 Release 10, ETSI TS 129 281 V10.1.0 (2011-05 https://www.etsi.org/deliver/etsi_ts/129200_129299/129281/10.01.00_60/ts_129281v100100p.pdf Octets 8 7 6 5 4 3 2 1 1 Version PT (*) E S PN 2 Message Type 3 Length (1st Octet) 4 Length (2nd Octet) 5 Tunnel Endpoint Identifier (1st Octet) 6 Tunnel Endpoint Identifier (2nd Octet) 7 Tunnel Endpoint Identifier (3rd Octet) 8 Tunnel Endpoint Identifier (4th Octet) 9 Sequence Number (1st Octet) 1) 4) 10 Sequence Number (2nd Octet) 1) 4) 11 N-PDU Number 2) 4) 12 Next Extension Header Type3) 4) */ // This structure includes only mandatory fields class __attribute__((__packed__)) gtp_v1_header_t { private: uint8_t flags = 0; uint8_t message_type = 0; uint16_t length = 0; uint32_t teid = 0; public: uint8_t get_message_type() const { return message_type; } uint16_t get_length_host_byte_order() const { return fast_ntoh(length); } uint32_t get_teid_host_byte_order() const { return fast_ntoh(teid); } std::string print() const { std::stringstream buffer; buffer << "flags: " << flags << " " << "message_type: " << uint32_t(message_type) << " " << "length: " << get_length_host_byte_order() << " " << "teid: " << get_teid_host_byte_order(); return buffer.str(); } }; static_assert(sizeof(gtp_v1_header_t) == 8, "Bad size for gtp_v1_header_t"); // This one includes optional sequence_number when only sequence_number flag is set class __attribute__((__packed__)) gtp_v1_header_with_sequence_t { private: uint8_t flags = 0; uint8_t message_type = 0; uint16_t length = 0; uint32_t teid = 0; uint16_t sequence_number = 0; // We do not evaluate it as we do not have n_pdu_number flag set but we still have it here // NOTE 2: 2) This field shall only be evaluated when indicated by the PN flag set to 1. // NOTE 4: 4) This field shall be present if and only if any one or more of the S, PN and E flags are set uint8_t n_pdu_number = 0; // We do not evaluate it as we do not have extension_header flag set but we still have it here // NOTE 3: 3) This field shall only be evaluated when indicated by the E flag set to 1 // NOTE 4: 4) This field shall be present if and only if any one or more of the S, PN and E flags are set. uint8_t next_extension_header_type = 0; public: uint16_t get_length_host_byte_order() const { return fast_ntoh(length); } uint8_t get_message_type() const { return message_type; } uint32_t get_teid_host_byte_order() const { return fast_ntoh(teid); } uint16_t get_sequence_number_host_byte_order() const { return fast_ntoh(sequence_number); } std::string print() const { std::stringstream buffer; buffer << "flags: " << flags << " " << "message_type: " << uint32_t(message_type) << " " << "length: " << get_length_host_byte_order() << " " << "teid: " << get_teid_host_byte_order() << " " << "sequence_number: " << get_sequence_number_host_byte_order(); return buffer.str(); } }; static_assert(sizeof(gtp_v1_header_with_sequence_t) == 12, "Bad size for gtp_v1_header_with_sequence_t"); // https://datatracker.ietf.org/doc/html/rfc2516#page-4 class __attribute__((__packed__)) pppoe_header_t { private: uint8_t version : 4 = 0, type : 4 = 0; uint8_t code = 0; uint16_t session_id = 0; uint16_t length = 0; public: uint8_t get_version() const { return version; } uint8_t get_type() const { return type; } uint8_t get_code() const { return code; } uint16_t get_session_id_host_byte_order() const { return fast_ntoh(session_id); } uint16_t get_length_host_byte_order() const { return fast_ntoh(length); } std::string print() const { std::stringstream buffer; buffer << "version: " << uint32_t(get_version()) << " " << "type: " << uint32_t(get_type()) << " " << "code: " << uint32_t(get_code()) << " " << "session_id: " << get_session_id_host_byte_order() << " " << "length: " << get_length_host_byte_order(); return buffer.str(); } }; static_assert(sizeof(pppoe_header_t) == 6, "Bad size for pppoe_header_t"); // https://datatracker.ietf.org/doc/html/rfc2784 // Updated GRE specification: RFC 2890 // https://datatracker.ietf.org/doc/html/rfc2890#section-2 /* * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |C| |K|S| Reserved0 | Ver | Protocol Type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum (optional) | Reserved1 (Optional) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Key (optional) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number (Optional) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ class __attribute__((__packed__)) gre_header_t { private: // We have dump capture20260318-2.pcap which has non zero key bit uint16_t flags; uint16_t protocol_type = 0; public: bool get_checksum() const { return fast_ntoh(flags) & 0b1000000000000000; } // It's not called that way in RFC but I got name from Wireshark bool get_routing_bit() const { return fast_ntoh(flags) & 0b0100000000000000; } bool get_key_bit() const { return fast_ntoh(flags) & 0b0010000000000000; } bool get_sequence_number_bit() const { return fast_ntoh(flags) & 0b0001000000000000; } uint16_t get_reserved() const { return (fast_ntoh(flags) & 0b0000111111111000) >> 3; } uint16_t get_version() const { return fast_ntoh(flags) & 0b0000000000000111; } uint16_t get_protocol_type_host_byte_order() const { return fast_ntoh(protocol_type); } std::string print() const { std::stringstream buffer; buffer << "checksum: " << get_checksum() << " " << "routing_bit: " << get_routing_bit() << " " << "key_bit: " << get_key_bit() << " " << "squence_number_bit: " << get_sequence_number_bit() << " " << "reserved: " << uint32_t(get_reserved()) << " " << "version: " << uint32_t(get_version()) << " " << "protocol_type: " << get_protocol_type_host_byte_order(); return buffer.str(); } }; static_assert(sizeof(gre_header_t) == 4, "Bad size for gre_header_t"); // It's tcp packet flags represented as bitfield for user friendly access to this flags class __attribute__((__packed__)) tcp_flags_as_uint16_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; std::string print() { std::stringstream buffer; buffer << "data_offset: " << uint32_t(data_offset) << " " << "reserved: " << uint32_t(reserved) << " " << "ns: " << uint32_t(ns) << " " << "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(); } }; class __attribute__((__packed__)) tcp_flags_t { public: uint16_t fin : 1, syn : 1, rst : 1, psh : 1, ack : 1, urg : 1, ece : 1, cwr : 1, ns : 1, reserved : 3, data_offset : 4; }; static_assert(sizeof(tcp_flags_t) == 2, "Bad size for tcp_flags_t"); // It's cropped TCP header and we use it only in cases when data was cropped (sFlow, inline monitoring services or cropped mirror) class __attribute__((__packed__)) cropped_tcp_header_only_ports_t { // These fields must not be accessed directly as they need decoding private: uint16_t source_port = 0; uint16_t destination_port = 0; public: uint16_t get_source_port_host_byte_order() const { return fast_ntoh(source_port); } uint16_t get_destination_port_host_byte_order() const { return fast_ntoh(destination_port); } }; static_assert(sizeof(cropped_tcp_header_only_ports_t) == 4, "Bad size for cropped_tcp_header_only_ports_t"); class __attribute__((__packed__)) tcp_header_t { // These fields must not be accessed directly as they need decoding private: uint16_t source_port = 0; uint16_t destination_port = 0; uint32_t sequence_number = 0; uint32_t ack_number = 0; // Flags here encoded as tcp_flags_t uint16_t data_offset_and_flags_as_integer = 0; private: uint16_t window_size = 0; uint16_t checksum = 0; uint16_t urgent = 0; public: uint16_t get_source_port_host_byte_order() const { return fast_ntoh(source_port); } uint16_t get_destination_port_host_byte_order() const { return fast_ntoh(destination_port); } uint32_t get_sequence_number_host_byte_order() const { return fast_ntoh(sequence_number); } uint32_t get_ack_number_host_byte_order() const { return fast_ntoh(ack_number); } uint16_t get_window_size_host_byte_order() const { return fast_ntoh(window_size); } uint16_t get_checksum_host_byte_order() const { return fast_ntoh(checksum); } uint16_t get_urgent_host_byte_order() const { return fast_ntoh(urgent); } uint16_t get_data_offset_and_flags_host_byte_order() const { return fast_ntoh(data_offset_and_flags_as_integer); } void set_data_offset(uint8_t data_offset) { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; tcp_flags->data_offset = data_offset; // Re-encode into network byte order again data_offset_and_flags_as_integer = fast_hton(data_offset_and_flags_as_integer_litle_endian); } bool get_fin() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->fin == 1; } bool get_syn() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->syn == 1; } bool get_rst() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->rst == 1; } bool get_psh() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->psh == 1; } bool get_ack() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->ack == 1; } bool get_urg() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->urg == 1; } bool get_ece() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->ece == 1; } bool get_cwr() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->cwr == 1; } bool get_ns() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->ns == 1; } uint8_t get_reserved() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->reserved; } uint8_t get_data_offset() const { uint16_t data_offset_and_flags_as_integer_litle_endian = fast_ntoh(data_offset_and_flags_as_integer); tcp_flags_t* tcp_flags = (tcp_flags_t*)&data_offset_and_flags_as_integer_litle_endian; return tcp_flags->data_offset; } std::string print() const { std::stringstream buffer; buffer << "source_port: " << get_source_port_host_byte_order() << " " << "destination_port: " << get_destination_port_host_byte_order() << " " << "sequence_number: " << get_sequence_number_host_byte_order() << " " << "ack_number: " << get_ack_number_host_byte_order() << " " << "data_offset: " << uint32_t(get_data_offset()) << " " << "reserved: " << uint32_t(get_reserved()) << " " << "ns: " << get_ns() << " " << "cwr: " << get_cwr() << " " << "ece: " << get_ece() << " " << "urg: " << get_urg() << " " << "ack: " << get_ack() << " " << "psh: " << get_psh() << " " << "rst: " << get_rst() << " " << "syn: " << get_syn() << " " << "fin: " << get_fin() << " " << "window_size: " << get_window_size_host_byte_order() << " " << "checksum: " << get_checksum_host_byte_order() << " " << "urgent: " << get_urgent_host_byte_order(); return buffer.str(); } }; static_assert(sizeof(tcp_header_t) == 20, "Bad size for tcp_header_t"); typedef uint8_t ipv6_address[16]; // Custom type for pretty printing typedef uint16_t ipv6_address_16bit_blocks[8]; inline std::string convert_ipv6_in_byte_array_to_string(const uint8_t (&v6_address)[16]) { std::stringstream buffer; uint16_t* pretty_print = (uint16_t*)v6_address; for (int i = 0; i < 8; i++) { buffer << std::hex << fast_ntoh(pretty_print[i]); if (i != 7) { buffer << ":"; } } return buffer.str(); } /* For full IPv6 support we should implement following option types: https://tools.ietf.org/html/rfc2460#page-7 Hop-by-Hop Options - IpProtocolNumberHOPOPT Routing (Type 0) - IpProtocolNumberIPV6_ROUTE Fragment - IpProtocolNumberIPV6_FRAG Destination Options - IpProtocolNumberIPV6_OPTS Authentication - IpProtocolNumberAH Encapsulating Security Payload - IpProtocolNumberESP */ class __attribute__((__packed__)) ipv6_fragment_header_flags { public: // fragment_offset is a number of 8byte chunk uint16_t more_fragments : 1, reserved2 : 2, fragment_offset : 13; }; static_assert(sizeof(ipv6_fragment_header_flags) == 2, "Bad size for ipv6_header_flags_t"); // IPv6 fragmentation header option class __attribute__((__packed__)) ipv6_extension_header_fragment_t { private: uint8_t next_header = 0; uint8_t reserved1 = 0; // Multiple flags in format ipv6_fragment_header_flags uint16_t fragmentation_and_flags_as_integer = 0; // We must not access these fields directly as they need proper decoding private: uint32_t identification = 0; public: uint16_t get_more_fragments() const { uint16_t flags_little_endian = fast_ntoh(fragmentation_and_flags_as_integer); ipv6_fragment_header_flags* flags = (ipv6_fragment_header_flags*)&flags_little_endian; return flags->more_fragments; } uint16_t get_reserved2() const { uint16_t flags_little_endian = fast_ntoh(fragmentation_and_flags_as_integer); ipv6_fragment_header_flags* flags = (ipv6_fragment_header_flags*)&flags_little_endian; return flags->reserved2; } uint16_t get_fragment_offset_8byte_chunks() const { uint16_t flags_little_endian = fast_ntoh(fragmentation_and_flags_as_integer); ipv6_fragment_header_flags* flags = (ipv6_fragment_header_flags*)&flags_little_endian; return flags->fragment_offset; } uint16_t get_fragment_offset_bytes() const { uint16_t flags_little_endian = fast_ntoh(fragmentation_and_flags_as_integer); ipv6_fragment_header_flags* flags = (ipv6_fragment_header_flags*)&flags_little_endian; return flags->fragment_offset * 8; } uint8_t get_next_header() const { return next_header; } uint8_t get_reserved1() const { return reserved1; } uint32_t get_identification_host_byte_order() const { return fast_ntoh(identification); } std::string print() const { std::stringstream buffer; buffer << "next_header: " << uint32_t(get_next_header()) << " " << "reserved1: " << uint32_t(get_reserved1()) << " " << "fragment_offset_bytes: " << uint32_t(get_fragment_offset_bytes()) << " " << "reserverd2: " << uint32_t(get_reserved2()) << " " << "more_fragments: " << uint32_t(get_more_fragments()) << " " << "identification: " << get_identification_host_byte_order(); return buffer.str(); } }; static_assert(sizeof(ipv6_extension_header_fragment_t) == 8, "Bad size for ipv6_extension_header_fragment_t"); class __attribute__((__packed__)) ipv6_header_flags_t { public: uint32_t flow_label : 20, traffic_class : 8, version : 4; }; static_assert(sizeof(ipv6_header_flags_t) == 4, "Bad size for ipv6_header_flags_t"); class __attribute__((__packed__)) ipv6_header_t { // We must not access these fields directly as they need decoding private: // Multiple flags carried in ipv6_header_flags_t uint32_t version_and_traffic_class_as_integer = 0; uint16_t payload_length = 0; uint8_t next_header = 0; uint8_t hop_limit = 0; public: ipv6_address source_address{}; ipv6_address destination_address{}; uint32_t get_flow_label() const { uint32_t version_and_traffic_class_little_endian = fast_ntoh(version_and_traffic_class_as_integer); ipv6_header_flags_t* header_flags = (ipv6_header_flags_t*)&version_and_traffic_class_little_endian; return header_flags->flow_label; } uint32_t get_traffic_class() const { uint32_t version_and_traffic_class_little_endian = fast_ntoh(version_and_traffic_class_as_integer); ipv6_header_flags_t* header_flags = (ipv6_header_flags_t*)&version_and_traffic_class_little_endian; return header_flags->traffic_class; } uint32_t get_version() const { uint32_t version_and_traffic_class_little_endian = fast_ntoh(version_and_traffic_class_as_integer); ipv6_header_flags_t* header_flags = (ipv6_header_flags_t*)&version_and_traffic_class_little_endian; return header_flags->version; } uint16_t get_payload_length() const { return fast_ntoh(payload_length); } uint8_t get_next_header() const { return next_header; } uint8_t get_hop_limit() const { return hop_limit; } std::string print() const { std::stringstream buffer; buffer << "version: " << get_version() << " " << "traffic_class: " << get_traffic_class() << " " << "flow_label: " << get_flow_label() << " " << "payload_length: " << get_payload_length() << " " << "next_header: " << uint32_t(get_next_header()) << " " << "hop_limit: " << uint32_t(get_hop_limit()) << " " << "source_address: " << convert_ipv6_in_byte_array_to_string(source_address) << " " << "destination_address: " << convert_ipv6_in_byte_array_to_string(destination_address); return buffer.str(); } }; static_assert(sizeof(ipv6_header_t) == 40, "Bad size for ipv6_header_t"); // It's class for fragmentation flag representation. It's pretty useful in some cases class __attribute__((__packed__)) ipv4_header_fragmentation_flags_t { public: // The offset value is the number of 8 byte blocks of data uint16_t fragment_offset : 13, more_fragments_flag : 1, dont_fragment_flag : 1, reserved_flag : 1; std::string print() { std::stringstream buffer; buffer << "fragment_offset: " << uint32_t(fragment_offset) << " " << "reserved_flag: " << uint32_t(reserved_flag) << " " << "dont_fragment_flag: " << uint32_t(dont_fragment_flag) << " " << "more_fragments_flag: " << uint32_t(more_fragments_flag); return buffer.str(); } }; static_assert(sizeof(ipv4_header_fragmentation_flags_t) == 2, "Bad size for ipv4_header_fragmentation_flags_t"); class __attribute__((__packed__)) ipv4_header_t { // We must not access these fields directly as they need conversion private: uint8_t ihl : 4 = 0, version : 4 = 0; uint8_t ecn : 2 = 0, dscp : 6 = 0; // This is the combined length of the header and the data uint16_t total_length = 0; uint16_t identification = 0; // There we have plenty of fragmentation specific fields encoded in ipv4_header_fragmentation_flags_t uint16_t fragmentation_details_as_integer = 0; uint8_t ttl = 0; uint8_t protocol = 0; uint16_t checksum = 0; uint32_t source_ip = 0; uint32_t destination_ip = 0; public: ipv4_header_t() : ihl(0), version(0), ecn(0), dscp(0), total_length(0), identification(0), fragmentation_details_as_integer(0), ttl(0), protocol(0), checksum(0), source_ip(0), destination_ip(0) { } uint16_t get_checksum_host_byte_order() const { return fast_ntoh(checksum); } uint16_t get_total_length_host_byte_order() const { return fast_ntoh(total_length); } bool is_fragmented() const { if (this->get_more_fragments_flag()) { return true; } if (this->get_fragment_offset_bytes() != 0) { return true; } return false; } uint8_t get_ihl() const { return ihl; } uint8_t get_version() const { return version; } uint8_t get_ecn() const { return ecn; } uint8_t get_dscp() const { return dscp; } uint16_t get_fragmentation_details_host_byte_order() { return fast_ntoh(fragmentation_details_as_integer); } // Returns fragment offset in number of 8 byte chunks uint16_t get_fragment_offset_8byte_chunks() const { uint16_t fragmenation_details_little_endian = fast_ntoh(fragmentation_details_as_integer); ipv4_header_fragmentation_flags_t* fragmentation_flags = (ipv4_header_fragmentation_flags_t*)&fragmenation_details_little_endian; return fragmentation_flags->fragment_offset; } // Returns offset in bytes uint16_t get_fragment_offset_bytes() const { uint16_t fragmenation_details_little_endian = fast_ntoh(fragmentation_details_as_integer); ipv4_header_fragmentation_flags_t* fragmentation_flags = (ipv4_header_fragmentation_flags_t*)&fragmenation_details_little_endian; // The offset value is the number of 8 byte blocks of data return fragmentation_flags->fragment_offset * 8; } bool get_more_fragments_flag() const { uint16_t fragmenation_details_little_endian = fast_ntoh(fragmentation_details_as_integer); ipv4_header_fragmentation_flags_t* fragmentation_flags = (ipv4_header_fragmentation_flags_t*)&fragmenation_details_little_endian; return fragmentation_flags->more_fragments_flag == 1; } bool get_dont_fragment_flag() const { uint16_t fragmenation_details_little_endian = fast_ntoh(fragmentation_details_as_integer); ipv4_header_fragmentation_flags_t* fragmentation_flags = (ipv4_header_fragmentation_flags_t*)&fragmenation_details_little_endian; return fragmentation_flags->dont_fragment_flag == 1; } uint16_t get_reserved_flag() const { uint16_t fragmenation_details_little_endian = fast_ntoh(fragmentation_details_as_integer); ipv4_header_fragmentation_flags_t* fragmentation_flags = (ipv4_header_fragmentation_flags_t*)&fragmenation_details_little_endian; return fragmentation_flags->reserved_flag; } // Clears reserved flag void clear_reserved_flag() { uint16_t fragmenation_details_little_endian = fast_ntoh(fragmentation_details_as_integer); ipv4_header_fragmentation_flags_t* fragmentation_flags = (ipv4_header_fragmentation_flags_t*)&fragmenation_details_little_endian; fragmentation_flags->reserved_flag = 0; // Re-encode back to network byte order fragmentation_details_as_integer = fast_hton(fragmenation_details_little_endian); } // Clears dont_fragment_flag flag void clear_dont_fragment_flag() { uint16_t fragmenation_details_little_endian = fast_ntoh(fragmentation_details_as_integer); ipv4_header_fragmentation_flags_t* fragmentation_flags = (ipv4_header_fragmentation_flags_t*)&fragmenation_details_little_endian; fragmentation_flags->dont_fragment_flag = 0; // Re-encode back to network byte order fragmentation_details_as_integer = fast_hton(fragmenation_details_little_endian); } // Value is a number of 8 byte blocks void set_fragment_offset_8byte_chunks(uint16_t value) { uint16_t fragmenation_details_little_endian = fast_ntoh(fragmentation_details_as_integer); ipv4_header_fragmentation_flags_t* fragmentation_flags = (ipv4_header_fragmentation_flags_t*)&fragmenation_details_little_endian; fragmentation_flags->fragment_offset = value; // Re-encode back to network byte order fragmentation_details_as_integer = fast_hton(fragmenation_details_little_endian); } uint32_t get_source_ip_network_byte_order() const { return source_ip; } uint32_t get_destination_ip_network_byte_order() const { return destination_ip; } uint32_t get_source_ip_host_byte_order() const { return fast_ntoh(source_ip); } uint32_t get_destination_ip_host_byte_order() const { return fast_ntoh(destination_ip); } uint16_t get_identification_host_byte_order() const { return fast_ntoh(identification); } uint8_t get_ttl() const { return ttl; } uint8_t get_protocol() const { return protocol; } std::string print() const { std::stringstream buffer; buffer << "version: " << uint32_t(get_version()) << " " << "ihl: " << uint32_t(get_ihl()) << " " << "dscp: " << uint32_t(get_dscp()) << " " << "ecn: " << uint32_t(get_ecn()) << " " << "length: " << get_total_length_host_byte_order() << " " << "identification: " << get_identification_host_byte_order() << " " << "fragment_offset_bytes: " << this->get_fragment_offset_bytes() << " " << "reserved_flag: " << uint32_t(get_reserved_flag()) << " " << "dont_fragment_flag: " << uint32_t(get_dont_fragment_flag()) << " " << "more_fragments_flag: " << uint32_t(get_more_fragments_flag()) << " " << "ttl: " << uint32_t(get_ttl()) << " " << "protocol: " << uint32_t(get_protocol()) << " " << "checksum: " << get_checksum_host_byte_order() << " " << "source_ip: " << convert_ip_as_little_endian_to_string(get_source_ip_host_byte_order()) << " " << "destination_ip: " << convert_ip_as_little_endian_to_string(get_destination_ip_host_byte_order()); return buffer.str(); } }; static_assert(sizeof(ipv4_header_t) == 20, "Bad size for ipv4_header_t"); pavel-odintsov-fastnetmon-394fbe0/src/networks_list000066400000000000000000000000001520703010000226510ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/networks_whitelist000066400000000000000000000000001520703010000237120ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/nlohmann/000077500000000000000000000000001520703010000216435ustar00rootroot00000000000000pavel-odintsov-fastnetmon-394fbe0/src/nlohmann/json.hpp000066400000000000000000035061341520703010000233410ustar00rootroot00000000000000// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT /****************************************************************************\ * Note on documentation: The source files contain links to the online * * documentation of the public API at https://json.nlohmann.me. This URL * * contains the most recent documentation and should also be applicable to * * previous versions; documentation for deprecated functions is not * * removed, but marked deprecated. See "Generate documentation" section in * * file docs/README.md. * \****************************************************************************/ #ifndef INCLUDE_NLOHMANN_JSON_HPP_ #define INCLUDE_NLOHMANN_JSON_HPP_ #include // all_of, find, for_each #include // nullptr_t, ptrdiff_t, size_t #include // hash, less #include // initializer_list #ifndef JSON_NO_IO #include // istream, ostream #endif // JSON_NO_IO #include // random_access_iterator_tag #include // unique_ptr #include // string, stoi, to_string #include // declval, forward, move, pair, swap #include // vector // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT // This file contains all macro definitions affecting or depending on the ABI #ifndef JSON_SKIP_LIBRARY_VERSION_CHECK #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0 #warning "Already included a different version of the library!" #endif #endif #endif #define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) #define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum) #define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum) #ifndef JSON_DIAGNOSTICS #define JSON_DIAGNOSTICS 0 #endif #ifndef JSON_DIAGNOSTIC_POSITIONS #define JSON_DIAGNOSTIC_POSITIONS 0 #endif #ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 #endif #if JSON_DIAGNOSTICS #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag #else #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS #endif #if JSON_DIAGNOSTIC_POSITIONS #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp #else #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS #endif #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp #else #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON #endif #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #endif // Construct the namespace ABI tags component #define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c #define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ _v ## major ## _ ## minor ## _ ## patch #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) #if NLOHMANN_JSON_NAMESPACE_NO_VERSION #define NLOHMANN_JSON_NAMESPACE_VERSION #else #define NLOHMANN_JSON_NAMESPACE_VERSION \ NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ NLOHMANN_JSON_VERSION_MINOR, \ NLOHMANN_JSON_VERSION_PATCH) #endif // Combine namespace components #define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b #define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) #ifndef NLOHMANN_JSON_NAMESPACE #define NLOHMANN_JSON_NAMESPACE \ nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ NLOHMANN_JSON_ABI_TAGS, \ NLOHMANN_JSON_NAMESPACE_VERSION) #endif #ifndef NLOHMANN_JSON_NAMESPACE_BEGIN #define NLOHMANN_JSON_NAMESPACE_BEGIN \ namespace nlohmann \ { \ inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ NLOHMANN_JSON_ABI_TAGS, \ NLOHMANN_JSON_NAMESPACE_VERSION) \ { #endif #ifndef NLOHMANN_JSON_NAMESPACE_END #define NLOHMANN_JSON_NAMESPACE_END \ } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ } // namespace nlohmann #endif // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // transform #include // array #include // forward_list #include // inserter, front_inserter, end #include // map #ifdef JSON_HAS_CPP_17 #include // optional #endif #include // string #include // tuple, make_tuple #include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible #include // unordered_map #include // pair, declval #include // valarray // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // nullptr_t #include // exception #if JSON_DIAGNOSTICS #include // accumulate #endif #include // runtime_error #include // to_string #include // vector // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // array #include // size_t #include // uint8_t #include // string // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // declval, pair // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT // #include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { template struct make_void { using type = void; }; template using void_t = typename make_void::type; } // namespace detail NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { // https://en.cppreference.com/w/cpp/experimental/is_detected struct nonesuch { nonesuch() = delete; ~nonesuch() = delete; nonesuch(nonesuch const&) = delete; nonesuch(nonesuch const&&) = delete; void operator=(nonesuch const&) = delete; void operator=(nonesuch&&) = delete; }; template class Op, class... Args> struct detector { using value_t = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { using value_t = std::true_type; using type = Op; }; template class Op, class... Args> using is_detected = typename detector::value_t; template class Op, class... Args> struct is_detected_lazy : is_detected { }; template class Op, class... Args> using detected_t = typename detector::type; template class Op, class... Args> using detected_or = detector; template class Op, class... Args> using detected_or_t = typename detected_or::type; template class Op, class... Args> using is_detected_exact = std::is_same>; template class Op, class... Args> using is_detected_convertible = std::is_convertible, To>; } // namespace detail NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-FileCopyrightText: 2016 - 2021 Evan Nemerson // SPDX-License-Identifier: MIT /* Hedley - https://nemequ.github.io/hedley * Created by Evan Nemerson */ #if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) #if defined(JSON_HEDLEY_VERSION) #undef JSON_HEDLEY_VERSION #endif #define JSON_HEDLEY_VERSION 15 #if defined(JSON_HEDLEY_STRINGIFY_EX) #undef JSON_HEDLEY_STRINGIFY_EX #endif #define JSON_HEDLEY_STRINGIFY_EX(x) #x #if defined(JSON_HEDLEY_STRINGIFY) #undef JSON_HEDLEY_STRINGIFY #endif #define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) #if defined(JSON_HEDLEY_CONCAT_EX) #undef JSON_HEDLEY_CONCAT_EX #endif #define JSON_HEDLEY_CONCAT_EX(a,b) a##b #if defined(JSON_HEDLEY_CONCAT) #undef JSON_HEDLEY_CONCAT #endif #define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) #if defined(JSON_HEDLEY_CONCAT3_EX) #undef JSON_HEDLEY_CONCAT3_EX #endif #define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c #if defined(JSON_HEDLEY_CONCAT3) #undef JSON_HEDLEY_CONCAT3 #endif #define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) #if defined(JSON_HEDLEY_VERSION_ENCODE) #undef JSON_HEDLEY_VERSION_ENCODE #endif #define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) #if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) #undef JSON_HEDLEY_VERSION_DECODE_MAJOR #endif #define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) #if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) #undef JSON_HEDLEY_VERSION_DECODE_MINOR #endif #define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) #if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) #undef JSON_HEDLEY_VERSION_DECODE_REVISION #endif #define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) #if defined(JSON_HEDLEY_GNUC_VERSION) #undef JSON_HEDLEY_GNUC_VERSION #endif #if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #elif defined(__GNUC__) #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) #endif #if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) #undef JSON_HEDLEY_GNUC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_GNUC_VERSION) #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_MSVC_VERSION) #undef JSON_HEDLEY_MSVC_VERSION #endif #if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) #elif defined(_MSC_FULL_VER) && !defined(__ICL) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) #elif defined(_MSC_VER) && !defined(__ICL) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) #endif #if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) #undef JSON_HEDLEY_MSVC_VERSION_CHECK #endif #if !defined(JSON_HEDLEY_MSVC_VERSION) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) #elif defined(_MSC_VER) && (_MSC_VER >= 1400) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) #elif defined(_MSC_VER) && (_MSC_VER >= 1200) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) #else #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) #endif #if defined(JSON_HEDLEY_INTEL_VERSION) #undef JSON_HEDLEY_INTEL_VERSION #endif #if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) #elif defined(__INTEL_COMPILER) && !defined(__ICL) #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) #endif #if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) #undef JSON_HEDLEY_INTEL_VERSION_CHECK #endif #if defined(JSON_HEDLEY_INTEL_VERSION) #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_INTEL_CL_VERSION) #undef JSON_HEDLEY_INTEL_CL_VERSION #endif #if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) #endif #if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK #endif #if defined(JSON_HEDLEY_INTEL_CL_VERSION) #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_PGI_VERSION) #undef JSON_HEDLEY_PGI_VERSION #endif #if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) #endif #if defined(JSON_HEDLEY_PGI_VERSION_CHECK) #undef JSON_HEDLEY_PGI_VERSION_CHECK #endif #if defined(JSON_HEDLEY_PGI_VERSION) #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_SUNPRO_VERSION) #undef JSON_HEDLEY_SUNPRO_VERSION #endif #if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) #elif defined(__SUNPRO_C) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) #elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) #elif defined(__SUNPRO_CC) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) #endif #if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK #endif #if defined(JSON_HEDLEY_SUNPRO_VERSION) #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) #undef JSON_HEDLEY_EMSCRIPTEN_VERSION #endif #if defined(__EMSCRIPTEN__) #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) #endif #if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK #endif #if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_ARM_VERSION) #undef JSON_HEDLEY_ARM_VERSION #endif #if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) #elif defined(__CC_ARM) && defined(__ARMCC_VERSION) #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) #endif #if defined(JSON_HEDLEY_ARM_VERSION_CHECK) #undef JSON_HEDLEY_ARM_VERSION_CHECK #endif #if defined(JSON_HEDLEY_ARM_VERSION) #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_IBM_VERSION) #undef JSON_HEDLEY_IBM_VERSION #endif #if defined(__ibmxl__) #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) #elif defined(__xlC__) && defined(__xlC_ver__) #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) #elif defined(__xlC__) #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) #endif #if defined(JSON_HEDLEY_IBM_VERSION_CHECK) #undef JSON_HEDLEY_IBM_VERSION_CHECK #endif #if defined(JSON_HEDLEY_IBM_VERSION) #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_VERSION) #undef JSON_HEDLEY_TI_VERSION #endif #if \ defined(__TI_COMPILER_VERSION__) && \ ( \ defined(__TMS470__) || defined(__TI_ARM__) || \ defined(__MSP430__) || \ defined(__TMS320C2000__) \ ) #if (__TI_COMPILER_VERSION__ >= 16000000) #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #endif #if defined(JSON_HEDLEY_TI_VERSION_CHECK) #undef JSON_HEDLEY_TI_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_VERSION) #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL2000_VERSION) #undef JSON_HEDLEY_TI_CL2000_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL2000_VERSION) #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL430_VERSION) #undef JSON_HEDLEY_TI_CL430_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL430_VERSION) #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_ARMCL_VERSION) #undef JSON_HEDLEY_TI_ARMCL_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_ARMCL_VERSION) #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL6X_VERSION) #undef JSON_HEDLEY_TI_CL6X_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL6X_VERSION) #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL7X_VERSION) #undef JSON_HEDLEY_TI_CL7X_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL7X_VERSION) #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CLPRU_VERSION) #undef JSON_HEDLEY_TI_CLPRU_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CLPRU_VERSION) #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_CRAY_VERSION) #undef JSON_HEDLEY_CRAY_VERSION #endif #if defined(_CRAYC) #if defined(_RELEASE_PATCHLEVEL) #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) #else #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) #endif #endif #if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) #undef JSON_HEDLEY_CRAY_VERSION_CHECK #endif #if defined(JSON_HEDLEY_CRAY_VERSION) #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_IAR_VERSION) #undef JSON_HEDLEY_IAR_VERSION #endif #if defined(__IAR_SYSTEMS_ICC__) #if __VER__ > 1000 #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) #else #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) #endif #endif #if defined(JSON_HEDLEY_IAR_VERSION_CHECK) #undef JSON_HEDLEY_IAR_VERSION_CHECK #endif #if defined(JSON_HEDLEY_IAR_VERSION) #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TINYC_VERSION) #undef JSON_HEDLEY_TINYC_VERSION #endif #if defined(__TINYC__) #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) #endif #if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) #undef JSON_HEDLEY_TINYC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TINYC_VERSION) #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_DMC_VERSION) #undef JSON_HEDLEY_DMC_VERSION #endif #if defined(__DMC__) #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) #endif #if defined(JSON_HEDLEY_DMC_VERSION_CHECK) #undef JSON_HEDLEY_DMC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_DMC_VERSION) #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_COMPCERT_VERSION) #undef JSON_HEDLEY_COMPCERT_VERSION #endif #if defined(__COMPCERT_VERSION__) #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) #endif #if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK #endif #if defined(JSON_HEDLEY_COMPCERT_VERSION) #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_PELLES_VERSION) #undef JSON_HEDLEY_PELLES_VERSION #endif #if defined(__POCC__) #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) #endif #if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) #undef JSON_HEDLEY_PELLES_VERSION_CHECK #endif #if defined(JSON_HEDLEY_PELLES_VERSION) #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_MCST_LCC_VERSION) #undef JSON_HEDLEY_MCST_LCC_VERSION #endif #if defined(__LCC__) && defined(__LCC_MINOR__) #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) #endif #if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_MCST_LCC_VERSION) #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_GCC_VERSION) #undef JSON_HEDLEY_GCC_VERSION #endif #if \ defined(JSON_HEDLEY_GNUC_VERSION) && \ !defined(__clang__) && \ !defined(JSON_HEDLEY_INTEL_VERSION) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_ARM_VERSION) && \ !defined(JSON_HEDLEY_CRAY_VERSION) && \ !defined(JSON_HEDLEY_TI_VERSION) && \ !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ !defined(__COMPCERT__) && \ !defined(JSON_HEDLEY_MCST_LCC_VERSION) #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION #endif #if defined(JSON_HEDLEY_GCC_VERSION_CHECK) #undef JSON_HEDLEY_GCC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_GCC_VERSION) #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_HAS_ATTRIBUTE) #undef JSON_HEDLEY_HAS_ATTRIBUTE #endif #if \ defined(__has_attribute) && \ ( \ (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ ) # define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) #else # define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) #else #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) #else #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE #endif #if \ defined(__has_cpp_attribute) && \ defined(__cplusplus) && \ (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) #else #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS #endif #if !defined(__cplusplus) || !defined(__has_cpp_attribute) #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) #elif \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_IAR_VERSION) && \ (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) #else #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE #endif #if defined(__has_cpp_attribute) && defined(__cplusplus) #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) #else #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE #endif #if defined(__has_cpp_attribute) && defined(__cplusplus) #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) #else #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_BUILTIN) #undef JSON_HEDLEY_HAS_BUILTIN #endif #if defined(__has_builtin) #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) #else #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) #undef JSON_HEDLEY_GNUC_HAS_BUILTIN #endif #if defined(__has_builtin) #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) #else #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) #undef JSON_HEDLEY_GCC_HAS_BUILTIN #endif #if defined(__has_builtin) #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) #else #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_FEATURE) #undef JSON_HEDLEY_HAS_FEATURE #endif #if defined(__has_feature) #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) #else #define JSON_HEDLEY_HAS_FEATURE(feature) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) #undef JSON_HEDLEY_GNUC_HAS_FEATURE #endif #if defined(__has_feature) #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) #else #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_FEATURE) #undef JSON_HEDLEY_GCC_HAS_FEATURE #endif #if defined(__has_feature) #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) #else #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_EXTENSION) #undef JSON_HEDLEY_HAS_EXTENSION #endif #if defined(__has_extension) #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) #else #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) #undef JSON_HEDLEY_GNUC_HAS_EXTENSION #endif #if defined(__has_extension) #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) #else #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) #undef JSON_HEDLEY_GCC_HAS_EXTENSION #endif #if defined(__has_extension) #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) #else #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) #else #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) #else #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) #else #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_WARNING) #undef JSON_HEDLEY_HAS_WARNING #endif #if defined(__has_warning) #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) #else #define JSON_HEDLEY_HAS_WARNING(warning) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_WARNING) #undef JSON_HEDLEY_GNUC_HAS_WARNING #endif #if defined(__has_warning) #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) #else #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_WARNING) #undef JSON_HEDLEY_GCC_HAS_WARNING #endif #if defined(__has_warning) #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) #else #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ defined(__clang__) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_PRAGMA(value) __pragma(value) #else #define JSON_HEDLEY_PRAGMA(value) #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) #undef JSON_HEDLEY_DIAGNOSTIC_PUSH #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_POP) #undef JSON_HEDLEY_DIAGNOSTIC_POP #endif #if defined(__clang__) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) #elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") #elif \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") #elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") #else #define JSON_HEDLEY_DIAGNOSTIC_PUSH #define JSON_HEDLEY_DIAGNOSTIC_POP #endif /* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ #endif #if defined(__cplusplus) # if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") # if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") # if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") # define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ xpr \ JSON_HEDLEY_DIAGNOSTIC_POP # else # define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ xpr \ JSON_HEDLEY_DIAGNOSTIC_POP # endif # else # define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ xpr \ JSON_HEDLEY_DIAGNOSTIC_POP # endif # endif #endif #if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x #endif #if defined(JSON_HEDLEY_CONST_CAST) #undef JSON_HEDLEY_CONST_CAST #endif #if defined(__cplusplus) # define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) #elif \ JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ ((T) (expr)); \ JSON_HEDLEY_DIAGNOSTIC_POP \ })) #else # define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) #endif #if defined(JSON_HEDLEY_REINTERPRET_CAST) #undef JSON_HEDLEY_REINTERPRET_CAST #endif #if defined(__cplusplus) #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) #else #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) #endif #if defined(JSON_HEDLEY_STATIC_CAST) #undef JSON_HEDLEY_STATIC_CAST #endif #if defined(__cplusplus) #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) #else #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) #endif #if defined(JSON_HEDLEY_CPP_CAST) #undef JSON_HEDLEY_CPP_CAST #endif #if defined(__cplusplus) # if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") # define JSON_HEDLEY_CPP_CAST(T, expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ ((T) (expr)) \ JSON_HEDLEY_DIAGNOSTIC_POP # elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) # define JSON_HEDLEY_CPP_CAST(T, expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("diag_suppress=Pe137") \ JSON_HEDLEY_DIAGNOSTIC_POP # else # define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) # endif #else # define JSON_HEDLEY_CPP_CAST(T, expr) (expr) #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #endif #if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") #elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) #elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) #elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") #elif \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") #elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") #elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) #elif \ JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") #elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") #elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) #elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) #elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") #elif \ JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") #elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #endif #if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") #elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION #endif #if JSON_HEDLEY_HAS_WARNING("-Wunused-function") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") #elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) #elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION #endif #if defined(JSON_HEDLEY_DEPRECATED) #undef JSON_HEDLEY_DEPRECATED #endif #if defined(JSON_HEDLEY_DEPRECATED_FOR) #undef JSON_HEDLEY_DEPRECATED_FOR #endif #if \ JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) #elif \ (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) #elif defined(__cplusplus) && (__cplusplus >= 201402L) #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") #else #define JSON_HEDLEY_DEPRECATED(since) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) #endif #if defined(JSON_HEDLEY_UNAVAILABLE) #undef JSON_HEDLEY_UNAVAILABLE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) #else #define JSON_HEDLEY_UNAVAILABLE(available_since) #endif #if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) #undef JSON_HEDLEY_WARN_UNUSED_RESULT #endif #if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) #elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #elif defined(_Check_return_) /* SAL */ #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ #else #define JSON_HEDLEY_WARN_UNUSED_RESULT #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) #endif #if defined(JSON_HEDLEY_SENTINEL) #undef JSON_HEDLEY_SENTINEL #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) #else #define JSON_HEDLEY_SENTINEL(position) #endif #if defined(JSON_HEDLEY_NO_RETURN) #undef JSON_HEDLEY_NO_RETURN #endif #if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_NO_RETURN __noreturn #elif \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define JSON_HEDLEY_NO_RETURN _Noreturn #elif defined(__cplusplus) && (__cplusplus >= 201103L) #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") #elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) #else #define JSON_HEDLEY_NO_RETURN #endif #if defined(JSON_HEDLEY_NO_ESCAPE) #undef JSON_HEDLEY_NO_ESCAPE #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) #else #define JSON_HEDLEY_NO_ESCAPE #endif #if defined(JSON_HEDLEY_UNREACHABLE) #undef JSON_HEDLEY_UNREACHABLE #endif #if defined(JSON_HEDLEY_UNREACHABLE_RETURN) #undef JSON_HEDLEY_UNREACHABLE_RETURN #endif #if defined(JSON_HEDLEY_ASSUME) #undef JSON_HEDLEY_ASSUME #endif #if \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_ASSUME(expr) __assume(expr) #elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) #elif \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) #if defined(__cplusplus) #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) #else #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) #endif #endif #if \ (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() #elif defined(JSON_HEDLEY_ASSUME) #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) #endif #if !defined(JSON_HEDLEY_ASSUME) #if defined(JSON_HEDLEY_UNREACHABLE) #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) #else #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) #endif #endif #if defined(JSON_HEDLEY_UNREACHABLE) #if \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) #else #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() #endif #else #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) #endif #if !defined(JSON_HEDLEY_UNREACHABLE) #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) #endif JSON_HEDLEY_DIAGNOSTIC_PUSH #if JSON_HEDLEY_HAS_WARNING("-Wpedantic") #pragma clang diagnostic ignored "-Wpedantic" #endif #if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" #endif #if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) #if defined(__clang__) #pragma clang diagnostic ignored "-Wvariadic-macros" #elif defined(JSON_HEDLEY_GCC_VERSION) #pragma GCC diagnostic ignored "-Wvariadic-macros" #endif #endif #if defined(JSON_HEDLEY_NON_NULL) #undef JSON_HEDLEY_NON_NULL #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) #else #define JSON_HEDLEY_NON_NULL(...) #endif JSON_HEDLEY_DIAGNOSTIC_POP #if defined(JSON_HEDLEY_PRINTF_FORMAT) #undef JSON_HEDLEY_PRINTF_FORMAT #endif #if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) #elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) #else #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) #endif #if defined(JSON_HEDLEY_CONSTEXPR) #undef JSON_HEDLEY_CONSTEXPR #endif #if defined(__cplusplus) #if __cplusplus >= 201103L #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) #endif #endif #if !defined(JSON_HEDLEY_CONSTEXPR) #define JSON_HEDLEY_CONSTEXPR #endif #if defined(JSON_HEDLEY_PREDICT) #undef JSON_HEDLEY_PREDICT #endif #if defined(JSON_HEDLEY_LIKELY) #undef JSON_HEDLEY_LIKELY #endif #if defined(JSON_HEDLEY_UNLIKELY) #undef JSON_HEDLEY_UNLIKELY #endif #if defined(JSON_HEDLEY_UNPREDICTABLE) #undef JSON_HEDLEY_UNPREDICTABLE #endif #if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) #endif #if \ (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) # define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) # define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) # define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) #elif \ (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) # define JSON_HEDLEY_PREDICT(expr, expected, probability) \ (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ (__extension__ ({ \ double hedley_probability_ = (probability); \ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ })) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ (__extension__ ({ \ double hedley_probability_ = (probability); \ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ })) # define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) # define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) #else # define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) # define JSON_HEDLEY_LIKELY(expr) (!!(expr)) # define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) #endif #if !defined(JSON_HEDLEY_UNPREDICTABLE) #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) #endif #if defined(JSON_HEDLEY_MALLOC) #undef JSON_HEDLEY_MALLOC #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_MALLOC __declspec(restrict) #else #define JSON_HEDLEY_MALLOC #endif #if defined(JSON_HEDLEY_PURE) #undef JSON_HEDLEY_PURE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) # define JSON_HEDLEY_PURE __attribute__((__pure__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) # define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") #elif defined(__cplusplus) && \ ( \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ ) # define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") #else # define JSON_HEDLEY_PURE #endif #if defined(JSON_HEDLEY_CONST) #undef JSON_HEDLEY_CONST #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_CONST __attribute__((__const__)) #elif \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_CONST _Pragma("no_side_effect") #else #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE #endif #if defined(JSON_HEDLEY_RESTRICT) #undef JSON_HEDLEY_RESTRICT #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) #define JSON_HEDLEY_RESTRICT restrict #elif \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ defined(__clang__) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_RESTRICT __restrict #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) #define JSON_HEDLEY_RESTRICT _Restrict #else #define JSON_HEDLEY_RESTRICT #endif #if defined(JSON_HEDLEY_INLINE) #undef JSON_HEDLEY_INLINE #endif #if \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ (defined(__cplusplus) && (__cplusplus >= 199711L)) #define JSON_HEDLEY_INLINE inline #elif \ defined(JSON_HEDLEY_GCC_VERSION) || \ JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) #define JSON_HEDLEY_INLINE __inline__ #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_INLINE __inline #else #define JSON_HEDLEY_INLINE #endif #if defined(JSON_HEDLEY_ALWAYS_INLINE) #undef JSON_HEDLEY_ALWAYS_INLINE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) # define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) # define JSON_HEDLEY_ALWAYS_INLINE __forceinline #elif defined(__cplusplus) && \ ( \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ ) # define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) # define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") #else # define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE #endif #if defined(JSON_HEDLEY_NEVER_INLINE) #undef JSON_HEDLEY_NEVER_INLINE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) #elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") #elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) #else #define JSON_HEDLEY_NEVER_INLINE #endif #if defined(JSON_HEDLEY_PRIVATE) #undef JSON_HEDLEY_PRIVATE #endif #if defined(JSON_HEDLEY_PUBLIC) #undef JSON_HEDLEY_PUBLIC #endif #if defined(JSON_HEDLEY_IMPORT) #undef JSON_HEDLEY_IMPORT #endif #if defined(_WIN32) || defined(__CYGWIN__) # define JSON_HEDLEY_PRIVATE # define JSON_HEDLEY_PUBLIC __declspec(dllexport) # define JSON_HEDLEY_IMPORT __declspec(dllimport) #else # if \ JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ ( \ defined(__TI_EABI__) && \ ( \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ ) \ ) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) # define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) # define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) # else # define JSON_HEDLEY_PRIVATE # define JSON_HEDLEY_PUBLIC # endif # define JSON_HEDLEY_IMPORT extern #endif #if defined(JSON_HEDLEY_NO_THROW) #undef JSON_HEDLEY_NO_THROW #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) #define JSON_HEDLEY_NO_THROW __declspec(nothrow) #else #define JSON_HEDLEY_NO_THROW #endif #if defined(JSON_HEDLEY_FALL_THROUGH) #undef JSON_HEDLEY_FALL_THROUGH #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) #elif defined(__fallthrough) /* SAL */ #define JSON_HEDLEY_FALL_THROUGH __fallthrough #else #define JSON_HEDLEY_FALL_THROUGH #endif #if defined(JSON_HEDLEY_RETURNS_NON_NULL) #undef JSON_HEDLEY_RETURNS_NON_NULL #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) #elif defined(_Ret_notnull_) /* SAL */ #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ #else #define JSON_HEDLEY_RETURNS_NON_NULL #endif #if defined(JSON_HEDLEY_ARRAY_PARAM) #undef JSON_HEDLEY_ARRAY_PARAM #endif #if \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ !defined(__STDC_NO_VLA__) && \ !defined(__cplusplus) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_TINYC_VERSION) #define JSON_HEDLEY_ARRAY_PARAM(name) (name) #else #define JSON_HEDLEY_ARRAY_PARAM(name) #endif #if defined(JSON_HEDLEY_IS_CONSTANT) #undef JSON_HEDLEY_IS_CONSTANT #endif #if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) #undef JSON_HEDLEY_REQUIRE_CONSTEXPR #endif /* JSON_HEDLEY_IS_CONSTEXPR_ is for HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ #if defined(JSON_HEDLEY_IS_CONSTEXPR_) #undef JSON_HEDLEY_IS_CONSTEXPR_ #endif #if \ JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) #endif #if !defined(__cplusplus) # if \ JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) #if defined(__INTPTR_TYPE__) #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) #else #include #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) #endif # elif \ ( \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_IAR_VERSION)) || \ (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) #if defined(__INTPTR_TYPE__) #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) #else #include #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) #endif # elif \ defined(JSON_HEDLEY_GCC_VERSION) || \ defined(JSON_HEDLEY_INTEL_VERSION) || \ defined(JSON_HEDLEY_TINYC_VERSION) || \ defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ defined(__clang__) # define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ sizeof(void) != \ sizeof(*( \ 1 ? \ ((void*) ((expr) * 0L) ) : \ ((struct { char v[sizeof(void) * 2]; } *) 1) \ ) \ ) \ ) # endif #endif #if defined(JSON_HEDLEY_IS_CONSTEXPR_) #if !defined(JSON_HEDLEY_IS_CONSTANT) #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) #endif #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) #else #if !defined(JSON_HEDLEY_IS_CONSTANT) #define JSON_HEDLEY_IS_CONSTANT(expr) (0) #endif #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) #endif #if defined(JSON_HEDLEY_BEGIN_C_DECLS) #undef JSON_HEDLEY_BEGIN_C_DECLS #endif #if defined(JSON_HEDLEY_END_C_DECLS) #undef JSON_HEDLEY_END_C_DECLS #endif #if defined(JSON_HEDLEY_C_DECL) #undef JSON_HEDLEY_C_DECL #endif #if defined(__cplusplus) #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { #define JSON_HEDLEY_END_C_DECLS } #define JSON_HEDLEY_C_DECL extern "C" #else #define JSON_HEDLEY_BEGIN_C_DECLS #define JSON_HEDLEY_END_C_DECLS #define JSON_HEDLEY_C_DECL #endif #if defined(JSON_HEDLEY_STATIC_ASSERT) #undef JSON_HEDLEY_STATIC_ASSERT #endif #if \ !defined(__cplusplus) && ( \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ defined(_Static_assert) \ ) # define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) #elif \ (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) # define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) #else # define JSON_HEDLEY_STATIC_ASSERT(expr, message) #endif #if defined(JSON_HEDLEY_NULL) #undef JSON_HEDLEY_NULL #endif #if defined(__cplusplus) #if __cplusplus >= 201103L #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) #elif defined(NULL) #define JSON_HEDLEY_NULL NULL #else #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) #endif #elif defined(NULL) #define JSON_HEDLEY_NULL NULL #else #define JSON_HEDLEY_NULL ((void*) 0) #endif #if defined(JSON_HEDLEY_MESSAGE) #undef JSON_HEDLEY_MESSAGE #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") # define JSON_HEDLEY_MESSAGE(msg) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ JSON_HEDLEY_PRAGMA(message msg) \ JSON_HEDLEY_DIAGNOSTIC_POP #elif \ JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) #elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) #else # define JSON_HEDLEY_MESSAGE(msg) #endif #if defined(JSON_HEDLEY_WARNING) #undef JSON_HEDLEY_WARNING #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") # define JSON_HEDLEY_WARNING(msg) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ JSON_HEDLEY_PRAGMA(clang warning msg) \ JSON_HEDLEY_DIAGNOSTIC_POP #elif \ JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) #else # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) #endif #if defined(JSON_HEDLEY_REQUIRE) #undef JSON_HEDLEY_REQUIRE #endif #if defined(JSON_HEDLEY_REQUIRE_MSG) #undef JSON_HEDLEY_REQUIRE_MSG #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) # if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") # define JSON_HEDLEY_REQUIRE(expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ __attribute__((diagnose_if(!(expr), #expr, "error"))) \ JSON_HEDLEY_DIAGNOSTIC_POP # define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ __attribute__((diagnose_if(!(expr), msg, "error"))) \ JSON_HEDLEY_DIAGNOSTIC_POP # else # define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) # define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) # endif #else # define JSON_HEDLEY_REQUIRE(expr) # define JSON_HEDLEY_REQUIRE_MSG(expr,msg) #endif #if defined(JSON_HEDLEY_FLAGS) #undef JSON_HEDLEY_FLAGS #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) #else #define JSON_HEDLEY_FLAGS #endif #if defined(JSON_HEDLEY_FLAGS_CAST) #undef JSON_HEDLEY_FLAGS_CAST #endif #if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) # define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("warning(disable:188)") \ ((T) (expr)); \ JSON_HEDLEY_DIAGNOSTIC_POP \ })) #else # define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) #endif #if defined(JSON_HEDLEY_EMPTY_BASES) #undef JSON_HEDLEY_EMPTY_BASES #endif #if \ (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) #else #define JSON_HEDLEY_EMPTY_BASES #endif /* Remaining macros are deprecated. */ #if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK #endif #if defined(__clang__) #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) #else #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE #endif #define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) #if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE #endif #define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) #if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) #undef JSON_HEDLEY_CLANG_HAS_BUILTIN #endif #define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) #if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) #undef JSON_HEDLEY_CLANG_HAS_FEATURE #endif #define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) #if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) #undef JSON_HEDLEY_CLANG_HAS_EXTENSION #endif #define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) #if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE #endif #define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) #if defined(JSON_HEDLEY_CLANG_HAS_WARNING) #undef JSON_HEDLEY_CLANG_HAS_WARNING #endif #define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) #endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ // This file contains all internal macro definitions (except those affecting ABI) // You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them // #include // exclude unsupported compilers #if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) #if defined(__clang__) #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" #endif #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" #endif #endif #endif // C++ language standard detection // if the user manually specified the used c++ version this is skipped #if !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) #if (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) #define JSON_HAS_CPP_23 #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L) #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) #define JSON_HAS_CPP_14 #endif // the cpp 11 flag is always specified because it is the minimal required version #define JSON_HAS_CPP_11 #endif #ifdef __has_include #if __has_include() #include #endif #endif #if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) #ifdef JSON_HAS_CPP_17 #if defined(__cpp_lib_filesystem) #define JSON_HAS_FILESYSTEM 1 #elif defined(__cpp_lib_experimental_filesystem) #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 #elif !defined(__has_include) #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 #elif __has_include() #define JSON_HAS_FILESYSTEM 1 #elif __has_include() #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 #endif // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #endif // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #endif // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support #if defined(__clang_major__) && __clang_major__ < 7 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #endif // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support #if defined(_MSC_VER) && _MSC_VER < 1914 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #endif // no filesystem support before iOS 13 #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #endif // no filesystem support before macOS Catalina #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #endif #endif #endif #ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 #endif #ifndef JSON_HAS_FILESYSTEM #define JSON_HAS_FILESYSTEM 0 #endif #ifndef JSON_HAS_THREE_WAY_COMPARISON #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L #define JSON_HAS_THREE_WAY_COMPARISON 1 #else #define JSON_HAS_THREE_WAY_COMPARISON 0 #endif #endif #ifndef JSON_HAS_RANGES // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 #define JSON_HAS_RANGES 0 #elif defined(__cpp_lib_ranges) #define JSON_HAS_RANGES 1 #else #define JSON_HAS_RANGES 0 #endif #endif #ifndef JSON_HAS_STATIC_RTTI #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 #define JSON_HAS_STATIC_RTTI 1 #else #define JSON_HAS_STATIC_RTTI 0 #endif #endif #ifdef JSON_HAS_CPP_17 #define JSON_INLINE_VARIABLE inline #else #define JSON_INLINE_VARIABLE #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] #else #define JSON_NO_UNIQUE_ADDRESS #endif // disable documentation warnings on clang #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdocumentation" #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" #endif // allow disabling exceptions #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try #define JSON_CATCH(exception) catch(exception) #define JSON_INTERNAL_CATCH(exception) catch(exception) #else #include #define JSON_THROW(exception) std::abort() #define JSON_TRY if(true) #define JSON_CATCH(exception) if(false) #define JSON_INTERNAL_CATCH(exception) if(false) #endif // override exception macros #if defined(JSON_THROW_USER) #undef JSON_THROW #define JSON_THROW JSON_THROW_USER #endif #if defined(JSON_TRY_USER) #undef JSON_TRY #define JSON_TRY JSON_TRY_USER #endif #if defined(JSON_CATCH_USER) #undef JSON_CATCH #define JSON_CATCH JSON_CATCH_USER #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_CATCH_USER #endif #if defined(JSON_INTERNAL_CATCH_USER) #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER #endif // allow overriding assert #if !defined(JSON_ASSERT) #include // assert #define JSON_ASSERT(x) assert(x) #endif // allow to access some private functions (needed by the test suite) #if defined(JSON_TESTS_PRIVATE) #define JSON_PRIVATE_UNLESS_TESTED public #else #define JSON_PRIVATE_UNLESS_TESTED private #endif /*! @brief macro to briefly define a mapping between an enum and JSON @def NLOHMANN_JSON_SERIALIZE_ENUM @since version 3.4.0 */ #define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ template \ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ { \ return ej_pair.first == e; \ }); \ j = ((it != std::end(m)) ? it : std::begin(m))->second; \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ e = ((it != std::end(m)) ? it : std::begin(m))->first; \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They // may be removed in the future once the class is split. #define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ template class ObjectType, \ template class ArrayType, \ class StringType, class BooleanType, class NumberIntegerType, \ class NumberUnsignedType, class NumberFloatType, \ template class AllocatorType, \ template class JSONSerializer, \ class BinaryType, \ class CustomBaseClass> #define NLOHMANN_BASIC_JSON_TPL \ basic_json // Macros to simplify conversion from/to types #define NLOHMANN_JSON_EXPAND( x ) x #define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME #define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ NLOHMANN_JSON_PASTE64, \ NLOHMANN_JSON_PASTE63, \ NLOHMANN_JSON_PASTE62, \ NLOHMANN_JSON_PASTE61, \ NLOHMANN_JSON_PASTE60, \ NLOHMANN_JSON_PASTE59, \ NLOHMANN_JSON_PASTE58, \ NLOHMANN_JSON_PASTE57, \ NLOHMANN_JSON_PASTE56, \ NLOHMANN_JSON_PASTE55, \ NLOHMANN_JSON_PASTE54, \ NLOHMANN_JSON_PASTE53, \ NLOHMANN_JSON_PASTE52, \ NLOHMANN_JSON_PASTE51, \ NLOHMANN_JSON_PASTE50, \ NLOHMANN_JSON_PASTE49, \ NLOHMANN_JSON_PASTE48, \ NLOHMANN_JSON_PASTE47, \ NLOHMANN_JSON_PASTE46, \ NLOHMANN_JSON_PASTE45, \ NLOHMANN_JSON_PASTE44, \ NLOHMANN_JSON_PASTE43, \ NLOHMANN_JSON_PASTE42, \ NLOHMANN_JSON_PASTE41, \ NLOHMANN_JSON_PASTE40, \ NLOHMANN_JSON_PASTE39, \ NLOHMANN_JSON_PASTE38, \ NLOHMANN_JSON_PASTE37, \ NLOHMANN_JSON_PASTE36, \ NLOHMANN_JSON_PASTE35, \ NLOHMANN_JSON_PASTE34, \ NLOHMANN_JSON_PASTE33, \ NLOHMANN_JSON_PASTE32, \ NLOHMANN_JSON_PASTE31, \ NLOHMANN_JSON_PASTE30, \ NLOHMANN_JSON_PASTE29, \ NLOHMANN_JSON_PASTE28, \ NLOHMANN_JSON_PASTE27, \ NLOHMANN_JSON_PASTE26, \ NLOHMANN_JSON_PASTE25, \ NLOHMANN_JSON_PASTE24, \ NLOHMANN_JSON_PASTE23, \ NLOHMANN_JSON_PASTE22, \ NLOHMANN_JSON_PASTE21, \ NLOHMANN_JSON_PASTE20, \ NLOHMANN_JSON_PASTE19, \ NLOHMANN_JSON_PASTE18, \ NLOHMANN_JSON_PASTE17, \ NLOHMANN_JSON_PASTE16, \ NLOHMANN_JSON_PASTE15, \ NLOHMANN_JSON_PASTE14, \ NLOHMANN_JSON_PASTE13, \ NLOHMANN_JSON_PASTE12, \ NLOHMANN_JSON_PASTE11, \ NLOHMANN_JSON_PASTE10, \ NLOHMANN_JSON_PASTE9, \ NLOHMANN_JSON_PASTE8, \ NLOHMANN_JSON_PASTE7, \ NLOHMANN_JSON_PASTE6, \ NLOHMANN_JSON_PASTE5, \ NLOHMANN_JSON_PASTE4, \ NLOHMANN_JSON_PASTE3, \ NLOHMANN_JSON_PASTE2, \ NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) #define NLOHMANN_JSON_PASTE2(func, v1) func(v1) #define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) #define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) #define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) #define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) #define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) #define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) #define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) #define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) #define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) #define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) #define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) #define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) #define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) #define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) #define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) #define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) #define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) #define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) #define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) #define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) #define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) #define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) #define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) #define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) #define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) #define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) #define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) #define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) #define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) #define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) #define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) #define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) #define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) #define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) #define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) #define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) #define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) #define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) #define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) #define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) #define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) #define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) #define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) #define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) #define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) #define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) #define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) #define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) #define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) #define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) #define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) #define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) #define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) #define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) #define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) #define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) #define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) #define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) #define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) #define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) #define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) #define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) #define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; #define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); #define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1; /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE @since version 3.9.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ template::value, int> = 0> \ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ template::value, int> = 0> \ friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT @since version 3.11.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ template::value, int> = 0> \ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ template::value, int> = 0> \ friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE @since version 3.11.3 @sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ template::value, int> = 0> \ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE @since version 3.9.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ template::value, int> = 0> \ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ template::value, int> = 0> \ void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT @since version 3.11.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ template::value, int> = 0> \ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ template::value, int> = 0> \ void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE @since version 3.11.3 @sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ template::value, int> = 0> \ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE @since version 3.12.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ */ #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ template::value, int> = 0> \ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ template::value, int> = 0> \ friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT @since version 3.12.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ */ #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ template::value, int> = 0> \ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ template::value, int> = 0> \ friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE @since version 3.12.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ */ #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ template::value, int> = 0> \ friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE @since version 3.12.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ */ #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ template::value, int> = 0> \ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ template::value, int> = 0> \ void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT @since version 3.12.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ */ #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ template::value, int> = 0> \ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ template::value, int> = 0> \ void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE @since version 3.12.0 @sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ */ #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ template::value, int> = 0> \ void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } // inspired from https://stackoverflow.com/a/26745591 // allows calling any std function as if (e.g., with begin): // using std::begin; begin(x); // // it allows using the detected idiom to retrieve the return type // of such an expression #define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ namespace detail { \ using std::std_name; \ \ template \ using result_of_##std_name = decltype(std_name(std::declval()...)); \ } \ \ namespace detail2 { \ struct std_name##_tag \ { \ }; \ \ template \ std_name##_tag std_name(T&&...); \ \ template \ using result_of_##std_name = decltype(std_name(std::declval()...)); \ \ template \ struct would_call_std_##std_name \ { \ static constexpr auto const value = ::nlohmann::detail:: \ is_detected_exact::value; \ }; \ } /* namespace detail2 */ \ \ template \ struct would_call_std_##std_name : detail2::would_call_std_##std_name \ { \ } #ifndef JSON_USE_IMPLICIT_CONVERSIONS #define JSON_USE_IMPLICIT_CONVERSIONS 1 #endif #if JSON_USE_IMPLICIT_CONVERSIONS #define JSON_EXPLICIT #else #define JSON_EXPLICIT explicit #endif #ifndef JSON_DISABLE_ENUM_SERIALIZATION #define JSON_DISABLE_ENUM_SERIALIZATION 0 #endif #ifndef JSON_USE_GLOBAL_UDLS #define JSON_USE_GLOBAL_UDLS 1 #endif #if JSON_HAS_THREE_WAY_COMPARISON #include // partial_ordering #endif NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { /////////////////////////// // JSON type enumeration // /////////////////////////// /*! @brief the JSON type enumeration This enumeration collects the different JSON types. It is internally used to distinguish the stored values, and the functions @ref basic_json::is_null(), @ref basic_json::is_object(), @ref basic_json::is_array(), @ref basic_json::is_string(), @ref basic_json::is_boolean(), @ref basic_json::is_number() (with @ref basic_json::is_number_integer(), @ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), @ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and @ref basic_json::is_structured() rely on it. @note There are three enumeration entries (number_integer, number_unsigned, and number_float), because the library distinguishes these three types for numbers: @ref basic_json::number_unsigned_t is used for unsigned integers, @ref basic_json::number_integer_t is used for signed integers, and @ref basic_json::number_float_t is used for floating-point numbers or to approximate integers which do not fit in the limits of their respective type. @sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON value with the default value for a given type @since version 1.0.0 */ enum class value_t : std::uint8_t { null, ///< null value object, ///< object (unordered set of name/value pairs) array, ///< array (ordered collection of values) string, ///< string value boolean, ///< boolean value number_integer, ///< number value (signed integer) number_unsigned, ///< number value (unsigned integer) number_float, ///< number value (floating-point) binary, ///< binary array (ordered collection of bytes) discarded ///< discarded by the parser callback function }; /*! @brief comparison operator for JSON types Returns an ordering that is similar to Python: - order: null < boolean < number < object < array < string < binary - furthermore, each type is not smaller than itself - discarded values are not comparable - binary is represented as a b"" string in python and directly comparable to a string; however, making a binary array directly comparable with a string would be surprising behavior in a JSON file. @since version 1.0.0 */ #if JSON_HAS_THREE_WAY_COMPARISON inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* #else inline bool operator<(const value_t lhs, const value_t rhs) noexcept #endif { static constexpr std::array order = {{ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, 6 /* binary */ } }; const auto l_index = static_cast(lhs); const auto r_index = static_cast(rhs); #if JSON_HAS_THREE_WAY_COMPARISON if (l_index < order.size() && r_index < order.size()) { return order[l_index] <=> order[r_index]; // *NOPAD* } return std::partial_ordering::unordered; #else return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; #endif } // GCC selects the built-in operator< over an operator rewritten from // a user-defined spaceship operator // Clang, MSVC, and ICC select the rewritten candidate // (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) #if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) inline bool operator<(const value_t lhs, const value_t rhs) noexcept { return std::is_lt(lhs <=> rhs); // *NOPAD* } #endif } // namespace detail NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT // #include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { /*! @brief replace all occurrences of a substring by another string @param[in,out] s the string to manipulate; changed so that all occurrences of @a f are replaced with @a t @param[in] f the substring to replace with @a t @param[in] t the string to replace @a f @pre The search string @a f must not be empty. **This precondition is enforced with an assertion.** @since version 2.0.0 */ template inline void replace_substring(StringType& s, const StringType& f, const StringType& t) { JSON_ASSERT(!f.empty()); for (auto pos = s.find(f); // find first occurrence of f pos != StringType::npos; // make sure f was found s.replace(pos, f.size(), t), // replace with t, and pos = s.find(f, pos + t.size())) // find next occurrence of f {} } /*! * @brief string escaping as described in RFC 6901 (Sect. 4) * @param[in] s string to escape * @return escaped string * * Note the order of escaping "~" to "~0" and "/" to "~1" is important. */ template inline StringType escape(StringType s) { replace_substring(s, StringType{"~"}, StringType{"~0"}); replace_substring(s, StringType{"/"}, StringType{"~1"}); return s; } /*! * @brief string unescaping as described in RFC 6901 (Sect. 4) * @param[in] s string to unescape * @return unescaped string * * Note the order of escaping "~1" to "/" and "~0" to "~" is important. */ template static void unescape(StringType& s) { replace_substring(s, StringType{"~1"}, StringType{"/"}); replace_substring(s, StringType{"~0"}, StringType{"~"}); } } // namespace detail NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // size_t // #include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { /// struct to capture the start position of the current token struct position_t { /// the total number of characters read std::size_t chars_read_total = 0; /// the number of characters read in the current line std::size_t chars_read_current_line = 0; /// the number of lines read std::size_t lines_read = 0; /// conversion to size_t to preserve SAX interface constexpr operator size_t() const { return chars_read_total; } }; } // namespace detail NLOHMANN_JSON_NAMESPACE_END // #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-FileCopyrightText: 2018 The Abseil Authors // SPDX-License-Identifier: MIT #include // array #include // size_t #include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type #include // index_sequence, make_index_sequence, index_sequence_for // #include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { template using uncvref_t = typename std::remove_cv::type>::type; #ifdef JSON_HAS_CPP_14 // the following utilities are natively available in C++14 using std::enable_if_t; using std::index_sequence; using std::make_index_sequence; using std::index_sequence_for; #else // alias templates to reduce boilerplate template using enable_if_t = typename std::enable_if::type; // The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h // which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. //// START OF CODE FROM GOOGLE ABSEIL // integer_sequence // // Class template representing a compile-time integer sequence. An instantiation // of `integer_sequence` has a sequence of integers encoded in its // type through its template arguments (which is a common need when // working with C++11 variadic templates). `absl::integer_sequence` is designed // to be a drop-in replacement for C++14's `std::integer_sequence`. // // Example: // // template< class T, T... Ints > // void user_function(integer_sequence); // // int main() // { // // user_function's `T` will be deduced to `int` and `Ints...` // // will be deduced to `0, 1, 2, 3, 4`. // user_function(make_integer_sequence()); // } template struct integer_sequence { using value_type = T; static constexpr std::size_t size() noexcept { return sizeof...(Ints); } }; // index_sequence // // A helper template for an `integer_sequence` of `size_t`, // `absl::index_sequence` is designed to be a drop-in replacement for C++14's // `std::index_sequence`. template using index_sequence = integer_sequence; namespace utility_internal { template struct Extend; // Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. template struct Extend, SeqSize, 0> { using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; }; template struct Extend, SeqSize, 1> { using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; }; // Recursion helper for 'make_integer_sequence'. // 'Gen::type' is an alias for 'integer_sequence'. template struct Gen { using type = typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; }; template struct Gen { using type = integer_sequence; }; } // namespace utility_internal // Compile-time sequences of integers // make_integer_sequence // // This template alias is equivalent to // `integer_sequence`, and is designed to be a drop-in // replacement for C++14's `std::make_integer_sequence`. template using make_integer_sequence = typename utility_internal::Gen::type; // make_index_sequence // // This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, // and is designed to be a drop-in replacement for C++14's // `std::make_index_sequence`. template using make_index_sequence = make_integer_sequence; // index_sequence_for // // Converts a typename pack into an index sequence of the same length, and // is designed to be a drop-in replacement for C++14's // `std::index_sequence_for()` template using index_sequence_for = make_index_sequence; //// END OF CODE FROM GOOGLE ABSEIL #endif // dispatch utility (taken from ranges-v3) template struct priority_tag : priority_tag < N - 1 > {}; template<> struct priority_tag<0> {}; // taken from ranges-v3 template struct static_const { static JSON_INLINE_VARIABLE constexpr T value{}; }; #ifndef JSON_HAS_CPP_17 template constexpr T static_const::value; #endif template constexpr std::array make_array(Args&& ... args) { return std::array {{static_cast(std::forward(args))...}}; } } // namespace detail NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // numeric_limits #include // char_traits #include // tuple #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // random_access_iterator_tag // #include // #include // #include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { template struct iterator_types {}; template struct iterator_types < It, void_t> { using difference_type = typename It::difference_type; using value_type = typename It::value_type; using pointer = typename It::pointer; using reference = typename It::reference; using iterator_category = typename It::iterator_category; }; // This is required as some compilers implement std::iterator_traits in a way that // doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. template struct iterator_traits { }; template struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> : iterator_types { }; template struct iterator_traits::value>> { using iterator_category = std::random_access_iterator_tag; using value_type = T; using difference_type = ptrdiff_t; using pointer = T*; using reference = T&; }; } // namespace detail NLOHMANN_JSON_NAMESPACE_END // #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT // #include NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT // #include NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); NLOHMANN_JSON_NAMESPACE_END // #include // #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ #include // int64_t, uint64_t #include // map #include // allocator #include // string #include // vector // #include /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann @since version 1.0.0 */ NLOHMANN_JSON_NAMESPACE_BEGIN /*! @brief default JSONSerializer template argument This serializer ignores the template arguments and uses ADL ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) for serialization. */ template struct adl_serializer; /// a class to store JSON values /// @sa https://json.nlohmann.me/api/basic_json/ template class ObjectType = std::map, template class ArrayType = std::vector, class StringType = std::string, class BooleanType = bool, class NumberIntegerType = std::int64_t, class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, template class AllocatorType = std::allocator, template class JSONSerializer = adl_serializer, class BinaryType = std::vector, // cppcheck-suppress syntaxError class CustomBaseClass = void> class basic_json; /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document /// @sa https://json.nlohmann.me/api/json_pointer/ template class json_pointer; /*! @brief default specialization @sa https://json.nlohmann.me/api/json/ */ using json = basic_json<>; /// @brief a minimal map-like container that preserves insertion order /// @sa https://json.nlohmann.me/api/ordered_map/ template struct ordered_map; /// @brief specialization that maintains the insertion order of object keys /// @sa https://json.nlohmann.me/api/ordered_json/ using ordered_json = basic_json; NLOHMANN_JSON_NAMESPACE_END #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ NLOHMANN_JSON_NAMESPACE_BEGIN /*! @brief detail namespace with internal helper functions This namespace collects functions that should not be exposed, implementations of some @ref basic_json methods, and meta-programming helpers. @since version 2.1.0 */ namespace detail { ///////////// // helpers // ///////////// // Note to maintainers: // // Every trait in this file expects a non CV-qualified type. // The only exceptions are in the 'aliases for detected' section // (i.e. those of the form: decltype(T::member_function(std::declval()))) // // In this case, T has to be properly CV-qualified to constraint the function arguments // (e.g. to_json(BasicJsonType&, const T&)) template struct is_basic_json : std::false_type {}; NLOHMANN_BASIC_JSON_TPL_DECLARATION struct is_basic_json : std::true_type {}; // used by exceptions create() member functions // true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t // false_type otherwise template struct is_basic_json_context : std::integral_constant < bool, is_basic_json::type>::type>::value || std::is_same::value > {}; ////////////////////// // json_ref helpers // ////////////////////// template class json_ref; template struct is_json_ref : std::false_type {}; template struct is_json_ref> : std::true_type {}; ////////////////////////// // aliases for detected // ////////////////////////// template using mapped_type_t = typename T::mapped_type; template using key_type_t = typename T::key_type; template using value_type_t = typename T::value_type; template using difference_type_t = typename T::difference_type; template using pointer_t = typename T::pointer; template using reference_t = typename T::reference; template using iterator_category_t = typename T::iterator_category; template using to_json_function = decltype(T::to_json(std::declval()...)); template using from_json_function = decltype(T::from_json(std::declval()...)); template using get_template_function = decltype(std::declval().template get()); // trait checking if JSONSerializer::from_json(json const&, udt&) exists template struct has_from_json : std::false_type {}; // trait checking if j.get is valid // use this trait instead of std::is_constructible or std::is_convertible, // both rely on, or make use of implicit conversions, and thus fail when T // has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) template struct is_getable { static constexpr bool value = is_detected::value; }; template struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> { using serializer = typename BasicJsonType::template json_serializer; static constexpr bool value = is_detected_exact::value; }; // This trait checks if JSONSerializer::from_json(json const&) exists // this overload is used for non-default-constructible user-defined-types template struct has_non_default_from_json : std::false_type {}; template struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> { using serializer = typename BasicJsonType::template json_serializer; static constexpr bool value = is_detected_exact::value; }; // This trait checks if BasicJsonType::json_serializer::to_json exists // Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. template struct has_to_json : std::false_type {}; template struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> { using serializer = typename BasicJsonType::template json_serializer; static constexpr bool value = is_detected_exact::value; }; template using detect_key_compare = typename T::key_compare; template struct has_key_compare : std::integral_constant::value> {}; // obtains the actual object key comparator template struct actual_object_comparator { using object_t = typename BasicJsonType::object_t; using object_comparator_t = typename BasicJsonType::default_object_comparator_t; using type = typename std::conditional < has_key_compare::value, typename object_t::key_compare, object_comparator_t>::type; }; template using actual_object_comparator_t = typename actual_object_comparator::type; ///////////////// // char_traits // ///////////////// // Primary template of char_traits calls std char_traits template struct char_traits : std::char_traits {}; // Explicitly define char traits for unsigned char since it is not standard template<> struct char_traits : std::char_traits { using char_type = unsigned char; using int_type = uint64_t; // Redefine to_int_type function static int_type to_int_type(char_type c) noexcept { return static_cast(c); } static char_type to_char_type(int_type i) noexcept { return static_cast(i); } static constexpr int_type eof() noexcept { return static_cast(std::char_traits::eof()); } }; // Explicitly define char traits for signed char since it is not standard template<> struct char_traits : std::char_traits { using char_type = signed char; using int_type = uint64_t; // Redefine to_int_type function static int_type to_int_type(char_type c) noexcept { return static_cast(c); } static char_type to_char_type(int_type i) noexcept { return static_cast(i); } static constexpr int_type eof() noexcept { return static_cast(std::char_traits::eof()); } }; /////////////////// // is_ functions // /////////////////// // https://en.cppreference.com/w/cpp/types/conjunction template struct conjunction : std::true_type { }; template struct conjunction : B { }; template struct conjunction : std::conditional(B::value), conjunction, B>::type {}; // https://en.cppreference.com/w/cpp/types/negation template struct negation : std::integral_constant < bool, !B::value > { }; // Reimplementation of is_constructible and is_default_constructible, due to them being broken for // std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). // This causes compile errors in e.g. clang 3.5 or gcc 4.9. template struct is_default_constructible : std::is_default_constructible {}; template struct is_default_constructible> : conjunction, is_default_constructible> {}; template struct is_default_constructible> : conjunction, is_default_constructible> {}; template struct is_default_constructible> : conjunction...> {}; template struct is_default_constructible> : conjunction...> {}; template struct is_constructible : std::is_constructible {}; template struct is_constructible> : is_default_constructible> {}; template struct is_constructible> : is_default_constructible> {}; template struct is_constructible> : is_default_constructible> {}; template struct is_constructible> : is_default_constructible> {}; template struct is_iterator_traits : std::false_type {}; template struct is_iterator_traits> { private: using traits = iterator_traits; public: static constexpr auto value = is_detected::value && is_detected::value && is_detected::value && is_detected::value && is_detected::value; }; template struct is_range { private: using t_ref = typename std::add_lvalue_reference::type; using iterator = detected_t; using sentinel = detected_t; // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator // and https://en.cppreference.com/w/cpp/iterator/sentinel_for // but reimplementing these would be too much work, as a lot of other concepts are used underneath static constexpr auto is_iterator_begin = is_iterator_traits>::value; public: static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; }; template using iterator_t = enable_if_t::value, result_of_begin())>>; template using range_value_t = value_type_t>>; // The following implementation of is_complete_type is taken from // https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ // and is written by Xiang Fan who agreed to using it in this library. template struct is_complete_type : std::false_type {}; template struct is_complete_type : std::true_type {}; template struct is_compatible_object_type_impl : std::false_type {}; template struct is_compatible_object_type_impl < BasicJsonType, CompatibleObjectType, enable_if_t < is_detected::value&& is_detected::value >> { using object_t = typename BasicJsonType::object_t; // macOS's is_constructible does not play well with nonesuch... static constexpr bool value = is_constructible::value && is_constructible::value; }; template struct is_compatible_object_type : is_compatible_object_type_impl {}; template struct is_constructible_object_type_impl : std::false_type {}; template struct is_constructible_object_type_impl < BasicJsonType, ConstructibleObjectType, enable_if_t < is_detected::value&& is_detected::value >> { using object_t = typename BasicJsonType::object_t; static constexpr bool value = (is_default_constructible::value && (std::is_move_assignable::value || std::is_copy_assignable::value) && (is_constructible::value && std::is_same < typename object_t::mapped_type, typename ConstructibleObjectType::mapped_type >::value)) || (has_from_json::value || has_non_default_from_json < BasicJsonType, typename ConstructibleObjectType::mapped_type >::value); }; template struct is_constructible_object_type : is_constructible_object_type_impl {}; template struct is_compatible_string_type { static constexpr auto value = is_constructible::value; }; template struct is_constructible_string_type { // launder type through decltype() to fix compilation failure on ICPC #ifdef __INTEL_COMPILER using laundered_type = decltype(std::declval()); #else using laundered_type = ConstructibleStringType; #endif static constexpr auto value = conjunction < is_constructible, is_detected_exact>::value; }; template struct is_compatible_array_type_impl : std::false_type {}; template struct is_compatible_array_type_impl < BasicJsonType, CompatibleArrayType, enable_if_t < is_detected::value&& is_iterator_traits>>::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 !std::is_same>::value >> { static constexpr bool value = is_constructible>::value; }; template struct is_compatible_array_type : is_compatible_array_type_impl {}; template struct is_constructible_array_type_impl : std::false_type {}; template struct is_constructible_array_type_impl < BasicJsonType, ConstructibleArrayType, enable_if_t::value >> : std::true_type {}; template struct is_constructible_array_type_impl < BasicJsonType, ConstructibleArrayType, enable_if_t < !std::is_same::value&& !is_compatible_string_type::value&& is_default_constructible::value&& (std::is_move_assignable::value || std::is_copy_assignable::value)&& is_detected::value&& is_iterator_traits>>::value&& is_detected::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 !std::is_same>::value&& is_complete_type < detected_t>::value >> { using value_type = range_value_t; static constexpr bool value = std::is_same::value || has_from_json::value || has_non_default_from_json < BasicJsonType, value_type >::value; }; template struct is_constructible_array_type : is_constructible_array_type_impl {}; template struct is_compatible_integer_type_impl : std::false_type {}; template struct is_compatible_integer_type_impl < RealIntegerType, CompatibleNumberIntegerType, enable_if_t < std::is_integral::value&& std::is_integral::value&& !std::is_same::value >> { // is there an assert somewhere on overflows? using RealLimits = std::numeric_limits; using CompatibleLimits = std::numeric_limits; static constexpr auto value = is_constructible::value && CompatibleLimits::is_integer && RealLimits::is_signed == CompatibleLimits::is_signed; }; template struct is_compatible_integer_type : is_compatible_integer_type_impl {}; template struct is_compatible_type_impl: std::false_type {}; template struct is_compatible_type_impl < BasicJsonType, CompatibleType, enable_if_t::value >> { static constexpr bool value = has_to_json::value; }; template struct is_compatible_type : is_compatible_type_impl {}; template struct is_constructible_tuple : std::false_type {}; template struct is_constructible_tuple> : conjunction...> {}; template struct is_json_iterator_of : std::false_type {}; template struct is_json_iterator_of : std::true_type {}; template struct is_json_iterator_of : std::true_type {}; // checks if a given type T is a template specialization of Primary template