upstream-fastnetmon/0000755000175000017500000000000015060546725012670 5ustar memeupstream-fastnetmon/README.md0000664000175000017500000001144015060514305014136 0ustar memeCommunity Edition =========== It's 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://github.com/pavel-odintsov/fastnetmon) 🌟️ [Author](https://pavel-odintsov.com/) 📜️ [Author LinkedIN](https://www.linkedin.com/in/podintsov/) Legal -------------- Author and legal owner: Pavel Odintsov ### Installation - [Linux install instructions](https://fastnetmon.com/install/) - [macOS install instructions](https://formulae.brew.sh/formula/fastnetmon) - [FreeBSD port](https://www.freshports.org/net-mgmt/fastnetmon/) - [VyOS bundled support](https://vyos.io/) 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) 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 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://github.com/pavel-odintsov/fastnetmon/blob/5b960f76d6bf3dca2c80ef13a3776dfa544fb897/src/fastnetmon.conf#L227) or [GoBGP](https://github.com/pavel-odintsov/fastnetmon/blob/5b960f76d6bf3dca2c80ef13a3776dfa544fb897/src/fastnetmon.conf#L249) (recommended) - Full integration with [Clickhouse](https://github.com/pavel-odintsov/fastnetmon/blob/7f0ad9c6cd2db3856607aeed04b5e8125fad3124/src/fastnetmon.conf#L287) [InfluxDB](https://github.com/pavel-odintsov/fastnetmon/blob/5b960f76d6bf3dca2c80ef13a3776dfa544fb897/src/fastnetmon.conf#L275) and [Graphite](https://github.com/pavel-odintsov/fastnetmon/blob/5b960f76d6bf3dca2c80ef13a3776dfa544fb897/src/fastnetmon.conf#L314) - [API](https://github.com/pavel-odintsov/fastnetmon/blob/5b960f76d6bf3dca2c80ef13a3776dfa544fb897/src/fastnetmon.conf#L357) - [Redis](https://github.com/pavel-odintsov/fastnetmon/blob/5b960f76d6bf3dca2c80ef13a3776dfa544fb897/src/fastnetmon.conf#L211) integration - MongoDB protocol support compatible with native [MongoDB](https://github.com/pavel-odintsov/fastnetmon/blob/5b960f76d6bf3dca2c80ef13a3776dfa544fb897/src/fastnetmon.conf#L221) and [FerretDB](https://github.com/FerretDB/FerretDB) - VLAN untagging in mirror and sFlow modes - Capture attack fingerprints in PCAP format We track [multiple](https://github.com/pavel-odintsov/fastnetmon/blob/5b960f76d6bf3dca2c80ef13a3776dfa544fb897/src/fastnetmon_logic.cpp#L3033) 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://join.slack.com/t/fastnetmon/shared_invite/zt-1i2cutd07-qEafHVoJvAOV5ODlHFsLoQ) - 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.gg/Q4h9AUqFng) Follow us at social media: ------- - [LinkedIn](https://www.linkedin.com/in/podintsov/) 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) upstream-fastnetmon/THANKS.md0000664000175000017500000000413715060514305014176 0ustar memeThanks 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 upstream-fastnetmon/tests/0000775000175000017500000000000015060514305014021 5ustar memeupstream-fastnetmon/tests/Dockerfile.debian0000664000175000017500000000125215060514305017234 0ustar memeFROM debian:bookworm # non-interactive ENV DEBIAN_FRONTEND noninteractive # install build dependencies RUN apt-get update RUN apt-get install -y --no-install-recommends build-essential git ca-certificates cmake libssl-dev\ capnproto libcapnp-dev libelf-dev libbpf-dev libpcap-dev libgrpc-dev libgrpc++-dev libprotobuf-dev\ protobuf-compiler libprotoc-dev libprotobuf-dev protobuf-compiler-grpc libboost-dev\ libboost-serialization-dev libboost-thread-dev libboost-regex-dev libboost-program-options-dev\ libmongoc-dev liblog4cpp5-dev libncurses5-dev COPY src/ /src/ WORKDIR /src RUN mkdir build && cd build && cmake .. -DLINK_WITH_ABSL=ON\ && make -j$(nproc) upstream-fastnetmon/tests/Dockerfile.ubuntu-24.100000664000175000017500000000133715060514305020002 0ustar memeFROM ubuntu:24.10 # non-interactive ENV DEBIAN_FRONTEND noninteractive # install build dependencies RUN apt-get update RUN apt-get install -y --no-install-recommends build-essential git ca-certificates cmake libssl-dev\ capnproto libcapnp-dev libelf-dev libbpf-dev libpcap-dev libgrpc-dev libgrpc++-dev libprotobuf-dev\ protobuf-compiler libprotoc-dev libprotobuf-dev protobuf-compiler-grpc libboost-dev\ libboost-serialization-dev libboost-thread-dev libboost-regex-dev libboost-program-options-dev\ libmongoc-dev liblog4cpp5-dev libncurses5-dev # absl RUN apt-get install -y --no-install-recommends libabsl-dev COPY src/ /src/ WORKDIR /src RUN mkdir build && cd build && cmake .. -DLINK_WITH_ABSL=ON\ && make upstream-fastnetmon/tests/Dockerfile.ubuntu-24.040000664000175000017500000000133715060514305020005 0ustar memeFROM ubuntu:24.04 # non-interactive ENV DEBIAN_FRONTEND noninteractive # install build dependencies RUN apt-get update RUN apt-get install -y --no-install-recommends build-essential git ca-certificates cmake libssl-dev\ capnproto libcapnp-dev libelf-dev libbpf-dev libpcap-dev libgrpc-dev libgrpc++-dev libprotobuf-dev\ protobuf-compiler libprotoc-dev libprotobuf-dev protobuf-compiler-grpc libboost-dev\ libboost-serialization-dev libboost-thread-dev libboost-regex-dev libboost-program-options-dev\ libmongoc-dev liblog4cpp5-dev libncurses5-dev # absl RUN apt-get install -y --no-install-recommends libabsl-dev COPY src/ /src/ WORKDIR /src RUN mkdir build && cd build && cmake .. -DLINK_WITH_ABSL=ON\ && make upstream-fastnetmon/tests/Dockerfile.debian-clang-150000664000175000017500000000165415060514305020547 0ustar memeFROM debian:bookworm # non-interactive ENV DEBIAN_FRONTEND noninteractive # install build dependencies RUN apt-get update RUN apt-get install -y --no-install-recommends build-essential git ca-certificates cmake libssl-dev\ capnproto libcapnp-dev libelf-dev libbpf-dev libpcap-dev libgrpc-dev libgrpc++-dev libprotobuf-dev\ protobuf-compiler libprotoc-dev libprotobuf-dev protobuf-compiler-grpc libboost-dev\ libboost-serialization-dev libboost-thread-dev libboost-regex-dev libboost-program-options-dev\ libmongoc-dev liblog4cpp5-dev libncurses-dev # install clang-15 RUN apt-get install -y --no-install-recommends clang-15 # set clang-15 as default compiler RUN update-alternatives --install /usr/bin/cc cc /usr/bin/clang-15 100 RUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-15 100 COPY src/ /src/ WORKDIR /src RUN mkdir build && cd build && cmake .. -DLINK_WITH_ABSL=ON\ && make -j$(nproc) upstream-fastnetmon/tests/Dockerfile.debian-gcc-120000664000175000017500000000123315060514305020205 0ustar memeFROM gcc:12-bookworm # non-interactive ENV DEBIAN_FRONTEND noninteractive # install build dependencies RUN apt-get update RUN apt-get install -y --no-install-recommends build-essential git ca-certificates cmake libssl-dev\ capnproto libcapnp-dev libelf-dev libbpf-dev libpcap-dev libgrpc-dev libgrpc++-dev libprotobuf-dev\ protobuf-compiler libprotoc-dev libprotobuf-dev protobuf-compiler-grpc libboost-dev\ libboost-serialization-dev libboost-thread-dev libboost-regex-dev libboost-program-options-dev\ libmongoc-dev liblog4cpp5-dev COPY src/ /src/ WORKDIR /src RUN mkdir build && cd build && cmake .. -DLINK_WITH_ABSL=ON\ && make -j$(nproc) upstream-fastnetmon/tests/Dockerfile.ubuntu-24.04-afl++0000664000175000017500000000643415060514305020676 0ustar memeFROM ubuntu:24.04 MAINTAINER Evgeny Shtanov # non-interactive ENV DEBIAN_FRONTEND noninteractive # install build dependencies RUN apt-get update RUN apt-get install -y --no-install-recommends build-essential git ca-certificates cmake libssl-dev\ capnproto libcapnp-dev libelf-dev libbpf-dev libpcap-dev libgrpc-dev libgrpc++-dev libprotobuf-dev\ protobuf-compiler libprotoc-dev libprotobuf-dev protobuf-compiler-grpc libboost-dev\ libboost-serialization-dev libboost-thread-dev libboost-regex-dev libboost-program-options-dev\ libmongoc-dev liblog4cpp5-dev libncurses5-dev # absl RUN apt-get install -y clang llvm llvm-dev lld tmux curl wget gdb vim netcat-traditional RUN git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus && make && make install # Install cargo RUN curl https://sh.rustup.rs -sSf | sh -s -- -y RUN cp /root/.cargo/bin/* /bin/ # Install casr RUN mkdir casr && git clone https://github.com/ispras/casr/ RUN cd casr/ && cargo build --release && cargo install casr RUN cp /root/.cargo/bin/* /bin/ #Install preeny for desock.so RUN git clone https://github.com/zardus/preeny/ && \ cd preeny && mkdir build && cd build && \ cmake ../ && make desock && cp lib/libdesock.so /usr/lib COPY src/ /src/ WORKDIR /src # Prepare context RUN cp notify_about_attack.sh /usr/local/bin/notify_about_attack.sh && \ cp tests/fuzz/fastnetmon.conf /etc/ && \ mkdir -p /var/log/fastnetmon_attacks # build dir for AFL++ fuzzing RUN mkdir build_fuzz_harness && cd build_fuzz_harness && \ cmake .. -DLINK_WITH_ABSL=ON -DENABLE_FUZZ_TEST=ON -DCMAKE_CXX_COMPILER=afl-clang-lto++ \ -DCMAKE_CXX_FLAGS="-g -O0 -ggdb3 -fsanitize=address,bounds,undefined,null,float-divide-by-zero" && \ make -j$(nproc) # build dir for AFL++ fuzzing RUN tests/fuzz/scripts/afl_pers_mod_instr.sh netflow_plugin/netflow_collector.cpp && \ mkdir build_netflow_pers_mod && cd build_netflow_pers_mod && \ cmake .. -DLINK_WITH_ABSL=ON -DCMAKE_CXX_COMPILER=afl-clang-lto++ \ -DCMAKE_CXX_FLAGS="-g -O0 -ggdb3 -fsanitize=address,bounds,undefined,null,float-divide-by-zero" && \ make -j$(nproc) fastnetmon RUN tests/fuzz/scripts/afl_pers_mod_instr.sh sflow_plugin/sflow_collector.cpp && \ mkdir build_sflow_pers_mod && cd build_sflow_pers_mod && \ cmake .. -DLINK_WITH_ABSL=ON -DCMAKE_CXX_COMPILER=afl-clang-lto++ \ -DCMAKE_CXX_FLAGS="-g -O0 -ggdb3 -fsanitize=address,bounds,undefined,null,float-divide-by-zero" && \ make -j$(nproc) fastnetmon # Uncomment this section if you plan to debug the code. # # build dir for desock # RUN mkdir build_desock && cd build_desock && \ # cmake .. -DLINK_WITH_ABSL=ON -DENABLE_FUZZ_TEST_DESOCK=ON -DCMAKE_CXX_COMPILER=afl-clang-lto++ \ # -DCMAKE_CXX_FLAGS="-g -O0 -ggdb3 -fsanitize=address,bounds,undefined,null,float-divide-by-zero" && \ # make -j$(nproc) # # dir for verifying crashes and debug harnesses # RUN mkdir build_debug && cd build_debug && \ # cmake .. -DCMAKE_BUILD_TYPE=Debug -DLINK_WITH_ABSL=ON -DENABLE_FUZZ_TEST=ON -DCMAKE_CXX_COMPILER=afl-clang-lto++ \ # -DCMAKE_CXX_FLAGS="-g -O0 -ggdb3" && \ # make -j$(nproc) # # dir for verifying crashes on vanilla binary # RUN mkdir build_clean && cd build_clean && \ # cmake .. -DLINK_WITH_ABSL=ON && \ # make -j$(nproc) fastnetmonupstream-fastnetmon/.circleci/0000755000175000017500000000000015060514305014510 5ustar memeupstream-fastnetmon/.circleci/config.yml0000664000175000017500000005755515060514305016523 0ustar memeversion: 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" upstream-fastnetmon/docs/0000755000175000017500000000000015060514305013605 5ustar memeupstream-fastnetmon/docs/images/0000755000175000017500000000000015060514305015052 5ustar memeupstream-fastnetmon/docs/images/grafana_total.png0000664000175000017500000125305715060514305020401 0ustar memePNG  IHDR  iCCPICC ProfileHWXS[RI(H R tDH*vdQVtUĶ@֊(,I]_79sΙLfPdd DQA$ CDF(#mh 再4ֿWQ\HSxbn69<Po<;/G!Aq)Nc-)Nq2(?( 7+Х<{q9<B<.;{*d-R)19Q,E&d8'3,,FQEQҜaf b*')C|IKtIp¾+5LPB 1SڣႼN͊RGYa8!#x+_=b* 4XAzL'ڜ/ q83:T /|F$r6](0JnieGlXp.``/'p0_-ɊTc[YAQ:c#{k0'/2F GA$Y 7yO H|`Ќxz xWPeT+ڀTYo#<8p< >ٰ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`upstream-fastnetmon/docs/images/deploy.png0000644000175000017500000060261314230006537017065 0ustar memePNG  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$ِ@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`upstream-fastnetmon/docs/images/fastnetmon_screen.png0000644000175000017500000103134614230006537021306 0ustar memePNG  IHDR}sRGB pHYs%%IR$iTXtXML:com.adobe.xmp 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`- #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 upstream-fastnetmon/src/man/0000755000175000017500000000000014234153172014222 5ustar memeupstream-fastnetmon/src/man/fastnetmon_client.10000644000175000017500000000076614234153172020031 0ustar meme.\" 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) upstream-fastnetmon/src/man/fastnetmon.80000644000175000017500000000155514234153172016477 0ustar meme.\" 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) upstream-fastnetmon/src/.clang_formatter_excludes0000664000175000017500000000112415060514305020510 0ustar memenetmap_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 upstream-fastnetmon/src/netmap_plugin/0000755000175000017500000000000015060514305016306 5ustar memeupstream-fastnetmon/src/netmap_plugin/netmap_collector.hpp0000664000175000017500000000023415060514305022352 0ustar meme#ifndef NETMAP_PLUGIN_H #define NETMAP_PLUGIN_H #include "../fastnetmon_types.hpp" void start_netmap_collection(process_packet_pointer func_ptr); #endif upstream-fastnetmon/src/netmap_plugin/netmap_collector.cpp0000664000175000017500000002455615060514305022362 0ustar meme#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_ng((u_char*)buffer, len, len, packet, parser_options); if (result != network_data_stuctures::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(); } upstream-fastnetmon/src/pcap_plugin/0000755000175000017500000000000015060514305015745 5ustar memeupstream-fastnetmon/src/pcap_plugin/pcap_collector.hpp0000664000175000017500000000027615060514305021456 0ustar meme#pragma once #include "../fastnetmon_types.hpp" #include void start_pcap_collection(process_packet_pointer func_ptr); void stop_pcap_collection(); std::string get_pcap_stats(); upstream-fastnetmon/src/pcap_plugin/pcap_collector.cpp0000664000175000017500000001727515060514305021460 0ustar meme#include #include #include "../fastnetmon_plugin.hpp" #ifdef _WIN32 #include #else #include #include #include #include // struct arphdr #include #include #include #include #include #endif #include #include "../all_logcpp_libraries.hpp" #include "pcap_collector.hpp" // Standard shift for type DLT_EN10MB, Ethernet unsigned int DATA_SHIFT_VALUE = 14; /* Complete list of ethertypes: http://en.wikipedia.org/wiki/EtherType */ /* This is the decimal equivalent of the VLAN tag's ether frame type */ #define VLAN_ETHERTYPE 0x8100 #define IP_ETHERTYPE 0x0800 #define IP6_ETHERTYPE 0x86dd #define ARP_ETHERTYPE 0x0806 /* 802.1Q VLAN tags are 4 bytes long. */ #define VLAN_HDRLEN 4 #ifndef DLT_LINUX_SLL #define DLT_LINUX_SLL 113 #endif extern log4cpp::Category& logger; extern std::map configuration_map; // This variable name should be uniq for every plugin! process_packet_pointer pcap_process_func_ptr = NULL; // Enlarge receive buffer for PCAP for minimize packet drops unsigned int pcap_buffer_size_mbytes = 10; // pcap handler, we want it as global variable beacuse it used in singnal handler pcap_t* descr = NULL; char errbuf[PCAP_ERRBUF_SIZE]; struct pcap_pkthdr hdr; // Prototypes void parse_packet(u_char* user, struct pcap_pkthdr* packethdr, const u_char* packetptr); void pcap_main_loop(const char* dev); void start_pcap_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "Pcap plugin started"; pcap_process_func_ptr = func_ptr; std::string interface_for_listening = ""; if (configuration_map.count("interfaces") != 0) { interface_for_listening = configuration_map["interfaces"]; } logger << log4cpp::Priority::INFO << "Pcap will sniff interface: " << interface_for_listening; pcap_main_loop(interface_for_listening.c_str()); } void stop_pcap_collection() { // stop pcap loop pcap_breakloop(descr); } // We do not use this function now! It's buggy! void parse_packet(u_char* user, struct pcap_pkthdr* packethdr, const u_char* packetptr) { struct ip* iphdr; struct tcphdr* tcphdr; struct udphdr* udphdr; struct ether_header* eptr; /* net/ethernet.h */ eptr = (struct ether_header*)packetptr; if (ntohs(eptr->ether_type) == VLAN_ETHERTYPE) { // It's tagged traffic we should sjoft for 4 bytes for getting the data packetptr += DATA_SHIFT_VALUE + VLAN_HDRLEN; } else if (ntohs(eptr->ether_type) == IP_ETHERTYPE) { // Skip the datalink layer header and get the IP header fields. packetptr += DATA_SHIFT_VALUE; } else if (ntohs(eptr->ether_type) == IP6_ETHERTYPE or ntohs(eptr->ether_type) == ARP_ETHERTYPE) { // we know about it but does't not care now } else { // printf("Packet with non standard ethertype found: 0x%x\n", ntohs(eptr->ether_type)); } iphdr = (struct ip*)packetptr; // src/dst UO is an in_addr, http://man7.org/linux/man-pages/man7/ip.7.html uint32_t src_ip = iphdr->ip_src.s_addr; uint32_t dst_ip = iphdr->ip_dst.s_addr; // The ntohs() function converts the unsigned short integer netshort from network byte order to // host byte order unsigned int packet_length = ntohs(iphdr->ip_len); simple_packet_t current_packet; current_packet.source = MIRROR; current_packet.arrival_time = current_inaccurate_time; // Advance to the transport layer header then parse and display // the fields based on the type of hearder: tcp, udp or icmp packetptr += 4 * iphdr->ip_hl; switch (iphdr->ip_p) { case IPPROTO_TCP: tcphdr = (struct tcphdr*)packetptr; #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) || defined(__OpenBSD__) current_packet.source_port = ntohs(tcphdr->th_sport); #else current_packet.source_port = ntohs(tcphdr->source); #endif #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) || defined(__OpenBSD__) current_packet.destination_port = ntohs(tcphdr->th_dport); #else current_packet.destination_port = ntohs(tcphdr->dest); #endif break; case IPPROTO_UDP: udphdr = (struct udphdr*)packetptr; #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) || defined(__OpenBSD__) current_packet.source_port = ntohs(udphdr->uh_sport); #else current_packet.source_port = ntohs(udphdr->source); #endif #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) || defined(__OpenBSD__) current_packet.destination_port = ntohs(udphdr->uh_dport); #else current_packet.destination_port = ntohs(udphdr->dest); #endif break; case IPPROTO_ICMP: // there are no port for ICMP current_packet.source_port = 0; current_packet.destination_port = 0; break; } current_packet.protocol = iphdr->ip_p; current_packet.src_ip = src_ip; current_packet.dst_ip = dst_ip; current_packet.length = packet_length; // Do packet processing pcap_process_func_ptr(current_packet); } void pcap_main_loop(const char* dev) { char errbuf[PCAP_ERRBUF_SIZE]; /* open device for reading in promiscuous mode */ int promisc = 1; bpf_u_int32 maskp; /* subnet mask */ bpf_u_int32 netp; /* ip */ logger << log4cpp::Priority::INFO << "Start listening on " << dev; /* Get the network address and mask */ pcap_lookupnet(dev, &netp, &maskp, errbuf); descr = pcap_create(dev, errbuf); if (descr == NULL) { logger << log4cpp::Priority::ERROR << "pcap_create was failed with error: " << errbuf; exit(0); } // Setting up 1MB buffer int set_buffer_size_res = pcap_set_buffer_size(descr, pcap_buffer_size_mbytes * 1024 * 1024); if (set_buffer_size_res != 0) { if (set_buffer_size_res == PCAP_ERROR_ACTIVATED) { logger << log4cpp::Priority::ERROR << "Can't set buffer size because pcap already activated\n"; exit(1); } else { logger << log4cpp::Priority::ERROR << "Can't set buffer size due to error: " << set_buffer_size_res; exit(1); } } if (pcap_set_promisc(descr, promisc) != 0) { logger << log4cpp::Priority::ERROR << "Can't activate promisc mode for interface: " << dev; exit(1); } if (pcap_activate(descr) != 0) { logger << log4cpp::Priority::ERROR << "Call pcap_activate was failed: " << pcap_geterr(descr); exit(1); } // man pcap-linktype int link_layer_header_type = pcap_datalink(descr); if (link_layer_header_type == DLT_EN10MB) { DATA_SHIFT_VALUE = 14; } else if (link_layer_header_type == DLT_LINUX_SLL) { DATA_SHIFT_VALUE = 16; } else { logger << log4cpp::Priority::INFO << "We did not support link type:" << link_layer_header_type; exit(0); } pcap_loop(descr, -1, (pcap_handler)parse_packet, NULL); } std::string get_pcap_stats() { std::stringstream output_buffer; struct pcap_stat current_pcap_stats; if (pcap_stats(descr, ¤t_pcap_stats) == 0) { output_buffer << "PCAP statistics" << "\n" << "Received packets: " << current_pcap_stats.ps_recv << "\n" << "Dropped packets: " << current_pcap_stats.ps_drop << " (" << int((double)current_pcap_stats.ps_drop / current_pcap_stats.ps_recv * 100) << "%)" << "\n" << "Dropped by driver or interface: " << current_pcap_stats.ps_ifdrop << "\n"; } return output_buffer.str(); } upstream-fastnetmon/src/fast_priority_queue.cpp0000644000175000017500000000445614232165256020272 0ustar memebool 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; } } upstream-fastnetmon/src/.clang-format0000644000175000017500000000377314232165256016040 0ustar meme--- 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 ... upstream-fastnetmon/src/iana_ip_protocols.cpp0000664000175000017500000020621015060514305017657 0ustar meme#include "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); } upstream-fastnetmon/src/fixed_size_packet_storage.hpp0000664000175000017500000000264415060514305021371 0ustar meme#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] = {}; }; upstream-fastnetmon/src/ipfix_fields/0000775000175000017500000000000015060514305016113 5ustar memeupstream-fastnetmon/src/ipfix_fields/ipfix_rfc.cpp0000664000175000017500000006737015060514305020605 0ustar meme#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); } upstream-fastnetmon/src/ipfix_fields/ipfix_rfc.hpp0000664000175000017500000000151215060514305020574 0ustar meme#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; }; upstream-fastnetmon/src/ipfix_fields/ipfix_fields.csv0000664000175000017500000056447515060514305021322 0ustar memeElementID,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,,,,,,,,,, upstream-fastnetmon/src/filter.hpp0000664000175000017500000000057515060514305015453 0ustar meme#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); upstream-fastnetmon/src/packet_storage.hpp0000664000175000017500000000763115060514305017161 0ustar meme#pragma once #include "fastnetmon_pcap_format.hpp" #include #include #include "fastnetmon_types.hpp" #include "fixed_size_packet_storage.hpp" // This is dynamically allocated packet storage class packet_storage_t { public: packet_storage_t() { memory_pointer = NULL; memory_pos = NULL; buffer_size = 0; // TODO: fix hardcoded mtu size this!!! max_captured_packet_size = 1500; } bool allocate_buffer(unsigned int buffer_size_in_packets) { unsigned int memory_size_in_bytes = buffer_size_in_packets * (max_captured_packet_size + sizeof(fastnetmon_pcap_pkthdr_t)) + sizeof(fastnetmon_pcap_file_header_t); // std::cout << "We will allocate " << memory_size_in_bytes << std::endl; memory_pointer = (unsigned char*)malloc(memory_size_in_bytes); if (memory_pointer != NULL) { this->buffer_size = memory_size_in_bytes; memory_pos = memory_pointer; // Add header to newely allocated memory block return this->write_header(); } else { return false; } } bool write_binary_data(void* data_pointer, unsigned int length) { if (we_have_free_space_for_x_bytes(length)) { memcpy(memory_pos, data_pointer, length); memory_pos += length; return true; } else { return false; } } bool write_packet(void* payload_pointer, unsigned int 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); } fastnetmon_pcap_pkthdr_t pcap_packet_header; pcap_packet_header.ts_sec = current_time.tv_sec; pcap_packet_header.ts_usec = current_time.tv_usec; // Store full length of packet pcap_packet_header.orig_len = real_packet_length; pcap_packet_header.incl_len = captured_length; // We should not store packets packets with size exceeding maximum size for // this file if (captured_length > max_captured_packet_size) { return false; } if (!this->write_binary_data(&pcap_packet_header, sizeof(pcap_packet_header))) { return false; } return (this->write_binary_data(payload_pointer, pcap_packet_header.incl_len)); } bool we_have_free_space_for_x_bytes(unsigned int length) { if (this->get_used_memory() + length <= this->buffer_size) { return true; } else { return false; } } bool write_header() { fastnetmon_pcap_file_header_t pcap_header; fill_pcap_header(&pcap_header, max_captured_packet_size); return this->write_binary_data(&pcap_header, sizeof(pcap_header)); } int64_t get_used_memory() { return memory_pos - memory_pointer; } bool deallocate_buffer() { if (memory_pointer == NULL or buffer_size == 0) { return true; } free(this->memory_pointer); this->memory_pointer = NULL; this->memory_pos = NULL; this->buffer_size = 0; return true; } void* get_buffer_pointer() { return memory_pointer; } unsigned int get_max_captured_packet_size() { return this->max_captured_packet_size; } void set_max_captured_packet_size(unsigned int new_max_captured_packet_size) { this->max_captured_packet_size = new_max_captured_packet_size; } private: unsigned char* memory_pointer; unsigned char* memory_pos; unsigned int buffer_size; // We should not store packets with incl_len exceeding this value unsigned int max_captured_packet_size; }; upstream-fastnetmon/src/netflow_plugin/0000755000175000017500000000000015060514305016500 5ustar memeupstream-fastnetmon/src/netflow_plugin/netflow_meta_info.hpp0000664000175000017500000000437415060514305022722 0ustar meme#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; 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; }; upstream-fastnetmon/src/netflow_plugin/netflow_v9.hpp0000664000175000017500000001151015060514305021305 0ustar meme// Netflow v9 #include "../fast_endianless.hpp" #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 #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 // 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; }; upstream-fastnetmon/src/netflow_plugin/netflow_collector.cpp0000664000175000017500000006651215060514305022744 0ustar meme/* 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; // 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: // "< #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(); } upstream-fastnetmon/src/netflow_plugin/netflow_v9_collector.cpp0000664000175000017500000023653115060514305023362 0ustar meme#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 #include #include #include #include "../fastnetmon_configuration_scheme.hpp" // Access to inaccurate but fast time extern time_t current_inaccurate_time; extern log4cpp::Category& logger; extern fastnetmon_configuration_t fastnetmon_global_configuration; 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; // 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); // 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; 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_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)); 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, size_t flowset_length, uint32_t source_id, const std::string& client_addres_in_string_format) { const netflow9_options_header_common_t* options_template_header = (const netflow9_options_header_common_t*)pkt; if (flowset_length < sizeof(*options_template_header)) { logger << log4cpp::Priority::ERROR << "Short Netflow v9 options template header " << flowset_length << " bytes agent IP: " << client_addres_in_string_format; return false; } if (ntohs(options_template_header->flowset_id) != NETFLOW9_OPTIONS_FLOWSET_ID) { logger << log4cpp::Priority::ERROR << "Function process_netflow_v9_options_template " "expects only NETFLOW9_OPTIONS_FLOWSET_ID but got " "another id: " << ntohs(options_template_header->flowset_id) << " agent IP: " << client_addres_in_string_format; return false; } const netflow9_options_header_t* options_nested_header = (const netflow9_options_header_t*)(pkt + sizeof(*options_template_header)); if (flowset_length < sizeof(*options_template_header) + sizeof(*options_nested_header)) { logger << log4cpp::Priority::ERROR << "Could not read specific header for Netflow v9 options template. " << " Agent IP: " << client_addres_in_string_format; return false; } uint16_t template_id = fast_ntoh(options_nested_header->template_id); if (flowset_length < sizeof(*options_template_header) + sizeof(*options_nested_header) + fast_ntoh(options_nested_header->option_scope_length)) { logger << log4cpp::Priority::ERROR << "Could not read specific header for Netflow v9 options template: need more space for scope" << " agent IP: " << client_addres_in_string_format; return false; } // I'm going to skip scope processing right now const uint8_t* zone_address = pkt + sizeof(*options_template_header) + sizeof(*options_nested_header); 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 < fast_ntoh(options_nested_header->option_scope_length);) { netflow9_template_flowset_record_t* tmplr = (netflow9_template_flowset_record_t*)(zone_address + scopes_offset); scopes_total_size += fast_ntoh(tmplr->length); scopes_offset += sizeof(*tmplr); } const uint8_t* zone_address_without_skopes = zone_address + fast_ntoh(options_nested_header->option_scope_length); uint32_t offset = 0; uint32_t records_number = 0; std::vector template_records_map; uint32_t total_size = 0; uint32_t option_length = fast_ntoh(options_nested_header->option_length); for (; offset < option_length;) { records_number++; const netflow9_template_flowset_record_t* tmplr = (const netflow9_template_flowset_record_t*)(zone_address_without_skopes + offset); uint32_t record_type = fast_ntoh(tmplr->type); uint32_t record_length = fast_ntoh(tmplr->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(*tmplr); total_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_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, size_t flowset_length, uint32_t source_id, const std::string& client_addres_in_string_format, uint64_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; } 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 (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: // 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: // 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.read_packet_length_from_ip_header = true; auto result = parse_raw_packet_to_simple_packet_full_ng((u_char*)(data), full_packet_length, record_length, flow_meta.nested_packet, parser_options); if (result != network_data_stuctures::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: " << network_data_stuctures::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 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; } // 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 (false) { // 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); 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; { 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; } 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; } } } } // 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_ip_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; } // 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) { 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; } // uint32_t is a 4 byte integer. Any reason why we convert here 16 bit flowset_id to 32 bit? ... Strange uint32_t flowset_id = ntohs(dath->header.flowset_id); // logger<< log4cpp::Priority::INFO<<"We have data with flowset_id: // "<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 num_flowsets = (flowset_length - offset) / field_template->total_length; if (num_flowsets == 0 || num_flowsets > 0x4000) { logger << log4cpp::Priority::ERROR << "Invalid number of data flowsets, strange number of flows: " << num_flowsets; return false; } if (field_template->type == netflow_template_type_t::Data) { for (uint32_t i = 0; i < num_flowsets; i++) { // 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 < num_flowsets; i++) { if (pkt + offset + field_template->total_length > packet_end) { logger << log4cpp::Priority::ERROR << "We tried to read data outside packet end"; 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 uint32_t flowset_count_total = ntohs(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); uint32_t flowset_id = ntohs(flowset->flowset_id); uint32_t flowset_length = ntohs(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; } switch (flowset_id) { case 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; } break; case NETFLOW9_OPTIONS_FLOWSET_ID: netflow9_options_templates_number++; if (!process_netflow_v9_options_template(packet + offset, flowset_length, source_id, client_addres_in_string_format)) { return false; } break; default: 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; break; // interrupts only switch! } 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; } break; } // 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) { break; } } return true; } upstream-fastnetmon/src/netflow_plugin/ipfix_metrics.hpp0000664000175000017500000001520515060514305022063 0ustar meme#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; upstream-fastnetmon/src/netflow_plugin/ipfix_collector.hpp0000664000175000017500000000065515060514305022406 0ustar meme#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(); upstream-fastnetmon/src/netflow_plugin/netflow_v9_collector.hpp0000664000175000017500000000066615060514305023365 0ustar meme#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(); upstream-fastnetmon/src/netflow_plugin/netflow.cpp0000664000175000017500000000002715060514305020663 0ustar meme#include "netflow.hpp" upstream-fastnetmon/src/netflow_plugin/ipfix.hpp0000664000175000017500000001570315060514305020340 0ustar meme// 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"); upstream-fastnetmon/src/netflow_plugin/netflow_v5_collector.cpp0000664000175000017500000002121015060514305023340 0ustar meme#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_ip_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; } upstream-fastnetmon/src/netflow_plugin/netflow.hpp0000664000175000017500000000177415060514305020702 0ustar meme// 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; }; upstream-fastnetmon/src/netflow_plugin/netflow_collector.hpp0000664000175000017500000000047315060514305022743 0ustar meme#pragma once #include "../fastnetmon_types.hpp" // For testing bool process_netflow_packet(uint8_t* packet, uint32_t len, std::string& client_addres_in_string_format, uint32_t client_ipv4_address); void start_netflow_collection(process_packet_pointer func_ptr); std::vector get_netflow_stats(); upstream-fastnetmon/src/netflow_plugin/netflow_v5_metrics.hpp0000664000175000017500000000243215060514305023032 0ustar meme#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; upstream-fastnetmon/src/netflow_plugin/netflow_template.hpp0000664000175000017500000000721215060514305022566 0ustar meme#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); upstream-fastnetmon/src/netflow_plugin/netflow_v5.hpp0000664000175000017500000000401415060514305021302 0ustar meme// 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))) upstream-fastnetmon/src/netflow_plugin/netflow_v5_collector.hpp0000664000175000017500000000054715060514305023357 0ustar meme#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(); upstream-fastnetmon/src/netflow_plugin/ipfix_collector.cpp0000664000175000017500000034414715060514305022410 0ustar meme#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_ng(payload_shift, flow_meta.variable_field_length, flow_meta.variable_field_length, flow_meta.nested_packet, parser_options); if (result != network_data_stuctures::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: " << network_data_stuctures::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_ip_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; } } } } upstream-fastnetmon/src/simple_packet_parser_ng.cpp0000664000175000017500000006126115060514305021040 0ustar meme#include "simple_packet_parser_ng.hpp" #include "all_logcpp_libraries.hpp" #include "network_data_structures.hpp" #include "fast_library.hpp" #include #include #include using namespace network_data_stuctures; // By default, we do not touch MPLS // TODO: it's not working code yet bool decode_mpls = false; // We can strip only 3 nested vlans const uint32_t maximum_vlans_to_strip = 3; // GTPv1 encapsulation must use this port as destination // https://www.etsi.org/deliver/etsi_ts/129200_129299/129281/10.01.00_60/ts_129281v100100p.pdf const uint16_t gtp_port = 2152; // This is the type for packet which carries traffic const uint8_t gtp_v1_t_pdu_type = 255; // Our own native function to convert wire packet into simple_packet_t parser_code_t parse_raw_packet_to_simple_packet_full_ng(const uint8_t* pointer, int length_before_sampling, int captured_length, simple_packet_t& packet, const parser_options_t& parser_options) { // We are using pointer copy because we are changing it const uint8_t* local_pointer = pointer; // It's very nice for doing checks const uint8_t* end_pointer = pointer + captured_length; // Return error if it shorter then ethernet headers if (local_pointer + sizeof(ethernet_header_t) > end_pointer) { return parser_code_t::memory_violation; } const ethernet_header_t* ethernet_header = (const ethernet_header_t*)local_pointer; // Copy Ethernet MAC addresses to packet structure using native C++ approach to avoid touching memory with memcpy std::copy(std::begin(ethernet_header->source_mac), std::end(ethernet_header->source_mac), std::begin(packet.source_mac)); std::copy(std::begin(ethernet_header->destination_mac), std::end(ethernet_header->destination_mac), std::begin(packet.destination_mac)); local_pointer += sizeof(ethernet_header_t); // Copy ethertype as we may need to change it below uint16_t ethertype = ethernet_header->get_ethertype_host_byte_order(); uint32_t number_of_stripped_vlans = 0; // This loop will not start if ethertype is not VLAN while (ethertype == IanaEthertypeVLAN) { // Return error if it's shorter than vlan header if (local_pointer + sizeof(ethernet_vlan_header_t) > end_pointer) { return parser_code_t::memory_violation; } const ethernet_vlan_header_t* ethernet_vlan_header = (const ethernet_vlan_header_t*)local_pointer; // We've agreed that this field keeps only outermost vlan if (number_of_stripped_vlans == 0) { packet.vlan = ethernet_vlan_header->get_vlan_id_host_byte_order(); } local_pointer += sizeof(ethernet_vlan_header_t); number_of_stripped_vlans++; // We need to limit it to avoid possibility of attack which uses too many vlans tags to overload our parser if (number_of_stripped_vlans > maximum_vlans_to_strip) { return parser_code_t::too_many_nested_vlans; } // Change ethertype to vlan's ethertype ethertype = ethernet_vlan_header->get_ethertype_host_byte_order(); } if (decode_mpls) { if (ethertype == IanaEthertypeMPLS_unicast) { REPEAT_MPLS_STRIP: if (local_pointer + sizeof(mpls_label_t) > end_pointer) { return parser_code_t::memory_violation; } const mpls_label_t* mpls_label_header = (const mpls_label_t*)local_pointer; std::cout << "MPLS header: " << mpls_label_header->print() << std::endl; // Strip this MPLS label local_pointer += sizeof(mpls_label_t); // If it's not bottom of stack, repeat operation if (mpls_label_header->bottom_of_stack == 0) { goto REPEAT_MPLS_STRIP; } } } // Here we store IPv4 or IPv6 l4 protocol numbers uint8_t protocol = 0; if (ethertype == IanaEthertypeIPv4) { // Return error if pointer is shorter then IP header if (local_pointer + sizeof(ipv4_header_t) > end_pointer) { return parser_code_t::memory_violation; } const ipv4_header_t* ipv4_header = (const ipv4_header_t*)local_pointer; // Use network representation of addresses packet.src_ip = ipv4_header->get_source_ip_network_byte_order(); packet.dst_ip = ipv4_header->get_destination_ip_network_byte_order(); packet.ip_protocol_version = 4; packet.ttl = ipv4_header->get_ttl(); // We need this specific field for Flow Spec mitigation mode packet.ip_length = ipv4_header->get_total_length_host_byte_order(); packet.ip_dont_fragment = ipv4_header->get_dont_fragment_flag(); packet.ip_fragmented = ipv4_header->is_fragmented(); packet.ip_more_fragments = ipv4_header->get_more_fragments_flag(); // We must use special function to recover value in a format useable for our consumption // We must not read this field directly packet.ip_fragment_offset = ipv4_header->get_fragment_offset_bytes(); // We keep these variables to maintain backward compatibility with parse_raw_packet_to_simple_packet_full() packet.captured_payload_length = length_before_sampling; packet.payload_full_length = length_before_sampling; // Pointer to payload packet.payload_pointer = (void*)pointer; protocol = ipv4_header->get_protocol(); packet.protocol = protocol; if (parser_options.read_packet_length_from_ip_header) { packet.length = ipv4_header->get_total_length_host_byte_order(); } else { packet.length = length_before_sampling; } // We need to handle fragmented traffic. In case of IPv4 fragmentation only first packet carries UDP / TCP / other headers // and consequent packets simply lack of this information and we know only protocol for them. // We can consequent packets by non zero fragment_offset if (ipv4_header->get_fragment_offset_bytes() != 0) { // The best we can do it so stop processing here and report success return parser_code_t::success; } // Ignore all IP options and shift pointer to L3 payload local_pointer += 4 * ipv4_header->get_ihl(); } else if (ethertype == IanaEthertypeIPv6) { // Return error if pointer is shorter then IP header if (local_pointer + sizeof(ipv6_header_t) > end_pointer) { return parser_code_t::memory_violation; } const ipv6_header_t* ipv6_header = (const ipv6_header_t*)local_pointer; // TODO: we may use std::copy for it to avoid touching memory with memcpy memcpy(&packet.src_ipv6, ipv6_header->source_address, sizeof(packet.src_ipv6)); memcpy(&packet.dst_ipv6, ipv6_header->destination_address, sizeof(packet.dst_ipv6)); packet.ip_protocol_version = 6; packet.ttl = ipv6_header->get_hop_limit(); // We need this specific field for Flow Spec mitigation mode packet.ip_length = ipv6_header->get_payload_length(); // We keep these variables to maintain backward compatibility with parse_raw_packet_to_simple_packet_full() packet.captured_payload_length = length_before_sampling; packet.payload_full_length = length_before_sampling; // Pointer to payload packet.payload_pointer = (void*)pointer; protocol = ipv6_header->get_next_header(); packet.protocol = protocol; if (parser_options.read_packet_length_from_ip_header) { packet.length = ipv6_header->get_payload_length(); } else { packet.length = length_before_sampling; } // Just skip our simple IPv6 header and then code below will try to decode specific protocol local_pointer += sizeof(ipv6_header_t); // According to https://datatracker.ietf.org/doc/html/rfc8200#page-8 // these 6 options are mandatory for complete IPv6 implementations // // IpProtocolNumberHOPOPT = 0 // IpProtocolNumberIPV6_ROUTE = 43 // IpProtocolNumberIPV6_FRAG = 44 // IpProtocolNumberESP = 50 // IpProtocolNumberAH = 51 // IpProtocolNumberIPV6_OPTS = 60 // // https://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml // // We do not support all IPv6 options in current version of parser // Some options are extremely rare in The Wild Internet: https://stats.labs.apnic.net/cgi-bin/v6frag_worldmap?w=7&d=f if (protocol == IpProtocolNumberHOPOPT || protocol == IpProtocolNumberIPV6_ROUTE || protocol == IpProtocolNumberIPV6_FRAG || protocol == IpProtocolNumberIPV6_OPTS || protocol == IpProtocolNumberAH || protocol == IpProtocolNumberESP) { // We decided to parse only fragmentation header option as only this field may be found in the Wild if (protocol == IpProtocolNumberIPV6_FRAG) { const ipv6_extension_header_fragment_t* ipv6_extension_header_fragment = (const ipv6_extension_header_fragment_t*)local_pointer; // If we received this header then we assume that packet was fragmented packet.ip_fragmented = true; packet.ip_more_fragments = ipv6_extension_header_fragment->get_more_fragments(); packet.ip_fragment_offset = ipv6_extension_header_fragment->get_fragment_offset_bytes(); // We stop processing here as I believe that it's enough to know that this traffic was fragmented // We do not parse nested protocol in this case at all // If we observe first fragment of UDP datagram we may see header but for consequent packets we cannot do it // I think that's it's safer to avoid parsing such traffic deeper until we collect packet examples for all cases return parser_code_t::success; } return parser_code_t::no_ipv6_options_support; } } else if (ethertype == IanaEthertypeARP) { // it's not parser error of course but we need to have visibility about this case return parser_code_t::arp; } else { return parser_code_t::unknown_ethertype; } if (protocol == IpProtocolNumberTCP) { if (local_pointer + sizeof(tcp_header_t) > end_pointer) { // We observed that Huawei routers may send only first 52 bytes of header in sFlow mode // and it's not enough to accommodate whole TCP header which has length of 20 bytes and we can observe only // first 14 bytes and it happened in case of vlan presence // To offer better experience we will try retrieving only ports from TCP header as they're located in the beginning of packet if (local_pointer + sizeof(cropped_tcp_header_only_ports_t) > end_pointer) { // Sadly we cannot even retrieve port numbers and we have to discard this packet // Idea of reporting this packet as TCP protocol without ports information is not reasonable return parser_code_t::memory_violation; } // Use short TCP header which has only access to source and destination ports const cropped_tcp_header_only_ports_t* tcp_header = (const cropped_tcp_header_only_ports_t*)local_pointer; packet.source_port = tcp_header->get_source_port_host_byte_order(); packet.destination_port = tcp_header->get_destination_port_host_byte_order(); return parser_code_t::success; } const tcp_header_t* tcp_header = (const tcp_header_t*)local_pointer; packet.source_port = tcp_header->get_source_port_host_byte_order(); packet.destination_port = tcp_header->get_destination_port_host_byte_order(); // TODO: rework this code to use structs with bit fields packet.flags = tcp_header->get_fin() * 0x01 + tcp_header->get_syn() * 0x02 + tcp_header->get_rst() * 0x04 + tcp_header->get_psh() * 0x08 + tcp_header->get_ack() * 0x10 + tcp_header->get_urg() * 0x20; } else if (protocol == IpProtocolNumberUDP) { if (local_pointer + sizeof(udp_header_t) > end_pointer) { return parser_code_t::memory_violation; } const udp_header_t* udp_header = (const udp_header_t*)local_pointer; packet.source_port = udp_header->get_source_port_host_byte_order(); packet.destination_port = udp_header->get_destination_port_host_byte_order(); // GTPv1 T-PDU packet which carries IPv4 or IPv6 as UDP payload // Standard requires that only destination port is set to gtp_port but in all pcaps I've seen we have gtp_port // for both source and destination port if (parser_options.unpack_gtp_v1 && (packet.source_port == gtp_port || packet.destination_port == gtp_port)) { // We need to read flags first if (local_pointer + sizeof(gtp_v1_header_flags_t) > end_pointer) { return parser_code_t::memory_violation; } local_pointer += sizeof(udp_header_t); // Read flags first const gtp_v1_header_flags_t* gtp_v1_header_flags = (const gtp_v1_header_flags_t*)local_pointer; // That's how we distinguish our GTP traffic from all other packets if (gtp_v1_header_flags->version == 1 && gtp_v1_header_flags->protocol_type == 1) { // Keep parsing } else { // Stop processing and return packet as-is return parser_code_t::success; } // Then we have multiple options how packets may look if (gtp_v1_header_flags->extension_header == 0 && gtp_v1_header_flags->n_pdu_number == 0) { // Keep parsing as we can parse such packets } else { // We do not support packets with extension headers or n_pdu_number_in_place return parser_code_t::success; } if (gtp_v1_header_flags->sequence_number) { // We need to use structure with sequence number in place if (local_pointer + sizeof(gtp_v1_header_with_sequence_t) > end_pointer) { return parser_code_t::memory_violation; } const gtp_v1_header_with_sequence_t* gtp_v1_header = (const gtp_v1_header_with_sequence_t*)local_pointer; if (gtp_v1_header->get_message_type() != gtp_v1_t_pdu_type) { // We are not interested in other message types and return them as-is return parser_code_t::success; } // std::cout << "GTPv1: " << gtp_v1_header->print() << " header: " << gtp_v1_header_flags->print() << std::endl; local_pointer += sizeof(gtp_v1_header_with_sequence_t); // TODO: we need to add support for IPv6 here // This function will override all fields in original packet structure by new fields parser_options_t gtp_parser_options; gtp_parser_options.read_packet_length_from_ip_header = true; // We need to calculate how much data we have after all parsed fields until end of packet to pass it to // function below We have length in GTP header but I do not think taht we should trust it int remaining_packet_length = end_pointer - local_pointer; // Well, we need to reset ports for original packet as in case if we have protocol like ICMP internally // it will inherit them and it will look weird packet.source_port = 0; packet.destination_port = 0; parser_code_t nested_packet_parse_result = parse_raw_ipv4_packet_to_simple_packet_full_ng(local_pointer, remaining_packet_length, remaining_packet_length, packet, gtp_parser_options); return nested_packet_parse_result; } else { // GTPv1 packet header withotu sequence number if (local_pointer + sizeof(gtp_v1_header_t) > end_pointer) { return parser_code_t::memory_violation; } const gtp_v1_header_t* gtp_v1_header = (const gtp_v1_header_t*)local_pointer; if (gtp_v1_header->get_message_type() != gtp_v1_t_pdu_type) { // We are not interested in other message types and return them as-is return parser_code_t::success; } // std::cout << "GTPv1: " << gtp_v1_header->print() << " header: " << gtp_v1_header_flags->print() << std::endl; local_pointer += sizeof(gtp_v1_header_t); // TODO: we need to add support for IPv6 here // This function will override all fields in original packet structure by new fields parser_options_t gtp_parser_options; gtp_parser_options.read_packet_length_from_ip_header = true; // We need to calculate how much data we have after all parsed fields until end of packet to pass it to // function below We have length in GTP header but I do not think taht we should trust it int remaining_packet_length = end_pointer - local_pointer; // Well, we need to reset ports for original packet as in case if we have protocol like ICMP internally // it will inherit them and it will look weird packet.source_port = 0; packet.destination_port = 0; parser_code_t nested_packet_parse_result = parse_raw_ipv4_packet_to_simple_packet_full_ng(local_pointer, remaining_packet_length, remaining_packet_length, packet, gtp_parser_options); return nested_packet_parse_result; } } } else if (protocol == IpProtocolNumberGRE) { if (!parser_options.unpack_gre) { // We do not decode it automatically but we can report source and destination IPs for it to FNM processing return parser_code_t::success; } if (local_pointer + sizeof(gre_header_t) > end_pointer) { return parser_code_t::memory_violation; } const gre_header_t* gre_header = (const gre_header_t*)local_pointer; // Current version of parser does not handle these special codes and we just fail parsing process // These flags may extend length of GRE header and current logic is not ready to decode any of them if (gre_header->get_checksum() != 0 || gre_header->get_reserved() != 0 || gre_header->get_version() != 0) { return parser_code_t::broken_gre; } uint16_t gre_nested_protocol = gre_header->get_protocol_type_host_byte_order(); // We will try parsing IPv4 only for now if (gre_nested_protocol == IanaEthertypeIPv4) { local_pointer += sizeof(gre_header_t); // This function will override all fields in original packet structure by new fields parser_options_t gre_parser_options; gre_parser_options.read_packet_length_from_ip_header = true; // We need to calculate how much data we have after all parsed fields until end of packet to pass it to function below int remaining_packet_length = end_pointer - local_pointer; parser_code_t nested_packet_parse_result = parse_raw_ipv4_packet_to_simple_packet_full_ng(local_pointer, remaining_packet_length, remaining_packet_length, packet, gre_parser_options); return nested_packet_parse_result; } else if (gre_nested_protocol == IanaEthertypeERSPAN) { local_pointer += sizeof(gre_header_t); // We need to calculate how much data we have after all parsed fields until end of packet to pass it to function below int remaining_packet_length = end_pointer - local_pointer; parser_options_t erspan_parser_options; erspan_parser_options.read_packet_length_from_ip_header = true; // We do not decode it second time erspan_parser_options.unpack_gre = false; // We need to call same function because we have normal wire format encoded data with ethernet header here parser_code_t nested_packet_parse_result = parse_raw_packet_to_simple_packet_full_ng(local_pointer, remaining_packet_length, remaining_packet_length, packet, erspan_parser_options); return nested_packet_parse_result; } else { return parser_code_t::broken_gre; } } else { // That's fine, it's not some known protocol but we can export basic information retrieved from IP packet return parser_code_t::success; } return parser_code_t::success; } // Our own native function to convert IPv4 packet into simple_packet_t parser_code_t parse_raw_ipv4_packet_to_simple_packet_full_ng(const uint8_t* pointer, int length_before_sampling, int captured_length, simple_packet_t& packet, const parser_options_t& parser_options) { // We are using pointer copy because we are changing it const uint8_t* local_pointer = pointer; // It's very nice for doing checks const uint8_t* end_pointer = pointer + captured_length; // Here we store IPv4 or IPv6 l4 protocol numbers uint8_t protocol = 0; // Return error if pointer is shorter then IP header if (local_pointer + sizeof(ipv4_header_t) > end_pointer) { return parser_code_t::memory_violation; } ipv4_header_t* ipv4_header = (ipv4_header_t*)local_pointer; // Use network representation of addresses packet.src_ip = ipv4_header->get_source_ip_network_byte_order(); packet.dst_ip = ipv4_header->get_destination_ip_network_byte_order(); packet.ip_protocol_version = 4; packet.ttl = ipv4_header->get_ttl(); packet.ip_length = ipv4_header->get_total_length_host_byte_order(); packet.ip_dont_fragment = ipv4_header->get_dont_fragment_flag(); packet.ip_fragmented = ipv4_header->is_fragmented(); // We keep these variables to maintain backward compatibility with parse_raw_packet_to_simple_packet_full() packet.captured_payload_length = length_before_sampling; packet.payload_full_length = length_before_sampling; // Pointer to payload packet.payload_pointer = (void*)pointer; protocol = ipv4_header->get_protocol(); packet.protocol = protocol; if (parser_options.read_packet_length_from_ip_header) { packet.length = ipv4_header->get_total_length_host_byte_order(); } else { packet.length = length_before_sampling; } // Ignore all IP options and shift pointer to L3 payload local_pointer += 4 * ipv4_header->get_ihl(); if (protocol == IpProtocolNumberTCP) { if (local_pointer + sizeof(tcp_header_t) > end_pointer) { return parser_code_t::memory_violation; } tcp_header_t* tcp_header = (tcp_header_t*)local_pointer; packet.source_port = tcp_header->get_source_port_host_byte_order(); packet.destination_port = tcp_header->get_destination_port_host_byte_order(); // TODO: rework this code to use structs with bit fields packet.flags = tcp_header->get_fin() * 0x01 + tcp_header->get_syn() * 0x02 + tcp_header->get_rst() * 0x04 + tcp_header->get_psh() * 0x08 + tcp_header->get_ack() * 0x10 + tcp_header->get_urg() * 0x20; } else if (protocol == IpProtocolNumberUDP) { if (local_pointer + sizeof(udp_header_t) > end_pointer) { return parser_code_t::memory_violation; } udp_header_t* udp_header = (udp_header_t*)local_pointer; packet.source_port = udp_header->get_source_port_host_byte_order(); packet.destination_port = udp_header->get_destination_port_host_byte_order(); } else { // That's fine, it's not some known protocol but we can export basic information retrieved from IP packet return parser_code_t::success; } return parser_code_t::success; } upstream-fastnetmon/src/dynamic_binary_buffer.hpp0000664000175000017500000001453415060514305020507 0ustar meme#pragma once #include class dynamic_binary_buffer_t { public: dynamic_binary_buffer_t() : byte_storage(nullptr), maximum_internal_storage_size(0) { // std::cout << "Default constructor called" << std::endl; } 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->maximum_internal_storage_size = source.maximum_internal_storage_size; source.maximum_internal_storage_size = 0; this->errors_occured = source.errors_occured; source.errors_occured = false; } // We should set maximum buffer size here. // TODO: add ability to relocate memory of we need more memory bool set_maximum_buffer_size_in_bytes(ssize_t size) { // Already allocated if (byte_storage) { return false; } // With nothrow we are using new without exceptions byte_storage = new (std::nothrow) uint8_t[size]; if (byte_storage) { maximum_internal_storage_size = size; return true; } else { return false; } } ~dynamic_binary_buffer_t() { // std::cout << "Destructor called" << std::endl; if (byte_storage) { delete[] byte_storage; byte_storage = nullptr; maximum_internal_storage_size = 0; } } // So this implementation will be useful only for real object copies // For returning local variable from function compiler will do this job // perfectly: // https://en.wikipedia.org/wiki/Return_value_optimization dynamic_binary_buffer_t(const dynamic_binary_buffer_t& that) { this->maximum_internal_storage_size = that.maximum_internal_storage_size; // Copy internal pointer too! It's very important! this->internal_data_shift = that.internal_data_shift; // std::cout << "Copy constructor called" << std::endl; // std::cout << "Copy constructor will copy " << this->internal_size << " // bytes" << // std::endl; // We are copying all memory (unused too) if (this->maximum_internal_storage_size > 0) { // Allocate memory for new instance this->set_maximum_buffer_size_in_bytes(this->maximum_internal_storage_size); memcpy(this->byte_storage, that.byte_storage, that.maximum_internal_storage_size); } } // All this functions just append some data with certain length to buffer and // increase total // size // They are very similar to std::stringstream but for binary data only bool append_byte(uint8_t byte_value) { // Do bounds check if (internal_data_shift > maximum_internal_storage_size - 1) { errors_occured = true; return false; } byte_storage[internal_data_shift] = byte_value; internal_data_shift += sizeof(uint8_t); return true; } // Use reference as argument bool append_dynamic_buffer(dynamic_binary_buffer_t& dynamic_binary_buffer) { // In this case we are copying only used memory // TODO: Why +1? if (internal_data_shift + dynamic_binary_buffer.get_used_size() > maximum_internal_storage_size + 1) { errors_occured = true; return false; } return this->append_data_as_pointer(dynamic_binary_buffer.get_pointer(), dynamic_binary_buffer.get_used_size()); } bool append_data_as_pointer(const void* ptr, size_t length) { if (internal_data_shift + length > maximum_internal_storage_size + 1) { errors_occured = true; return false; } memcpy(byte_storage + internal_data_shift, ptr, length); internal_data_shift += length; return true; } template bool append_data_as_object_ptr(src_type* ptr) { if (internal_data_shift + sizeof(src_type) > maximum_internal_storage_size + 1) { errors_occured = true; return false; } memcpy(byte_storage + internal_data_shift, ptr, sizeof(src_type)); internal_data_shift += sizeof(src_type); return true; } // All functions below DO NOT CHANGE internal buffer position! They are very // low level and // should be avoided! // We could set arbitrary byte with this function bool set_byte(uint32_t byte_number, uint8_t byte_value) { // Do bounds check if (byte_number > maximum_internal_storage_size - 1) { errors_occured = true; return false; } byte_storage[byte_number] = byte_value; return true; } bool memcpy_from_ptr(uint32_t shift, const void* ptr, uint32_t length) { if (shift + length > maximum_internal_storage_size + 1) { errors_occured = true; return false; } memcpy(byte_storage + shift, ptr, length); return true; } // More user friendly version of previous function template bool memcpy_from_object_ptr(uint32_t shift, src_type* ptr) { if (shift + sizeof(src_type) > maximum_internal_storage_size + 1) { errors_occured = true; return false; } memcpy(byte_storage + shift, ptr, sizeof(src_type)); return true; } // Return full size (with non initialized data region too) uint32_t get_full_size() const { return maximum_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 maximum_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); upstream-fastnetmon/src/traffic_output_formats/0000775000175000017500000000000015060514305020237 5ustar memeupstream-fastnetmon/src/traffic_output_formats/protobuf/0000775000175000017500000000000015060514305022077 5ustar memeupstream-fastnetmon/src/traffic_output_formats/protobuf/traffic_data.proto0000664000175000017500000000412415060514305025574 0ustar memesyntax = "proto3"; enum TrafficDirection { // Value is not set TRAFFIC_DIRECTION_UNKNOWN = 0; // Traffic is coming to our address space TRAFFIC_DIRECTION_INCOMING = 1; // Traffic is coming from our address space TRAFFIC_DIRECTION_OUTGOING = 2; // Traffic where both source and destination IPs do not belong to our address space or non IP traffic (for example ARP) TRAFFIC_DIRECTION_OTHER = 3; // Traffic is going from our address space to our address space TRAFFIC_DIRECTION_INTERNAL = 4; }; enum TelemetryType { TELEMETRY_TYPE_UNKNOWN = 0; TELEMETRY_TYPE_MIRROR = 1; TELEMETRY_TYPE_SFLOW = 2; TELEMETRY_TYPE_NETFLOW = 3; TELEMETRY_TYPE_TERA_FLOW = 4; } // Our unified flow - packet message message TrafficData { // Timestamp in seconds uint64 timestamp_seconds = 1; // Timestamp in milliseconds uint64 timestamp_milliseconds = 2; // Type of plugin which received traffic TelemetryType telemetry_type = 3; // IP protocol version: 4 or 6 uint32 ip_version = 4; TrafficDirection traffic_direction = 5; // Sampling ratio uint64 sampling_ratio = 6; // Protocol field from IP packet uint32 protocol = 7; // Source and destination IPs for IPv4 (4 bytes) and IPv6 (16 bytes) bytes source_ip = 8; bytes destination_ip = 9; // Ports for UDP and TCP protocols uint32 source_port = 10; uint32 destination_port = 11; // Number of transferred packets uint64 packets = 12; // Total length in bytes for transferred packets uint64 octets = 13; // TTL for IPv4 or Hop Limit for IPv6 uint32 ttl = 14; // TCP flags encoded in bit set uint32 tcp_flags = 15; bool ip_fragmented = 16; bool ip_dont_fragment = 17; // Input and output interfaces uint64 input_interface = 18; uint64 output_interface = 19; // Autonomous system numbers uint32 source_asn = 20; uint32 destination_asn = 21; // IPv4 or IPv6 address of device which sent traffic data bytes agent_address = 22; } upstream-fastnetmon/src/traffic_output_formats/protobuf/protobuf_traffic_format.cpp0000664000175000017500000000637615060514305027525 0ustar meme#include "../../all_logcpp_libraries.hpp" #include "../../fastnetmon_types.hpp" #include "traffic_data.pb.h" // Encode simple packet into Protobuf bool write_simple_packet_to_protobuf(const simple_packet_t& packet, TrafficData& traffic_data) { // Numbers before field match fields from proto file: traffic_data.proto // 1 we use arrival_time as traffic telemetry protocols do not provide this time in a reliable manner traffic_data.set_timestamp_seconds(packet.arrival_time); // 2 our time source has second only precision and we cannot populate milliseconds yet traffic_data.set_timestamp_milliseconds(0); // 3 if (packet.source == MIRROR) { traffic_data.set_telemetry_type(TELEMETRY_TYPE_MIRROR); } else if (packet.source == SFLOW) { traffic_data.set_telemetry_type(TELEMETRY_TYPE_SFLOW); } else if (packet.source == NETFLOW) { traffic_data.set_telemetry_type(TELEMETRY_TYPE_NETFLOW); } else if (packet.source == TERAFLOW) { traffic_data.set_telemetry_type(TELEMETRY_TYPE_TERA_FLOW); } else { traffic_data.set_telemetry_type(TELEMETRY_TYPE_UNKNOWN); } // 4 traffic_data.set_ip_version(packet.ip_protocol_version); // 5 if (packet.packet_direction == INCOMING) { traffic_data.set_traffic_direction(TRAFFIC_DIRECTION_INCOMING); } else if (packet.packet_direction == OUTGOING) { traffic_data.set_traffic_direction(TRAFFIC_DIRECTION_OUTGOING); } else if (packet.packet_direction == INTERNAL) { traffic_data.set_traffic_direction(TRAFFIC_DIRECTION_INTERNAL); } else if (packet.packet_direction == OTHER) { traffic_data.set_traffic_direction(TRAFFIC_DIRECTION_OTHER); } else { traffic_data.set_traffic_direction(TRAFFIC_DIRECTION_UNKNOWN); } // 6 traffic_data.set_sampling_ratio(packet.sample_ratio); // 7 traffic_data.set_protocol(packet.protocol); // 8 and 9 if (packet.ip_protocol_version == 4) { traffic_data.set_source_ip(&packet.src_ip, sizeof(packet.src_ip)); traffic_data.set_destination_ip(&packet.dst_ip, sizeof(packet.dst_ip)); } else if (packet.ip_protocol_version == 6) { traffic_data.set_source_ip(&packet.src_ipv6, sizeof(packet.src_ipv6)); traffic_data.set_destination_ip(&packet.dst_ipv6, sizeof(packet.dst_ipv6)); } // 10 traffic_data.set_source_port(packet.source_port); // 11 traffic_data.set_destination_port(packet.destination_port); // 12 traffic_data.set_packets(packet.number_of_packets); // 13 traffic_data.set_octets(packet.length); // 14 traffic_data.set_ttl(packet.ttl); // 15 traffic_data.set_tcp_flags(packet.flags); // 16 traffic_data.set_ip_fragmented(packet.ip_fragmented); // 17 traffic_data.set_ip_dont_fragment(packet.ip_dont_fragment); // 18 and 19 traffic_data.set_input_interface(packet.input_interface); traffic_data.set_output_interface(packet.output_interface); // 20 and 21 traffic_data.set_source_asn(packet.src_asn); traffic_data.set_destination_asn(packet.dst_asn); // 22 // In current version we support only IPv4 agent IP traffic_data.set_agent_address(&packet.agent_ip_address, sizeof(packet.agent_ip_address)); return true; } upstream-fastnetmon/src/traffic_output_formats/protobuf/protobuf_traffic_format.hpp0000664000175000017500000000017615060514305027522 0ustar meme#include "traffic_data.pb.h" bool write_simple_packet_to_protobuf(const simple_packet_t& packet, TrafficData& traffic_data); upstream-fastnetmon/src/tests/0000755000175000017500000000000015060514305014606 5ustar memeupstream-fastnetmon/src/tests/patricia_performance_tests.cpp0000664000175000017500000001525615060514305022724 0ustar meme#include #include #include #include #include #include #include #include #include #include #include #include #include "../nlohmann/json.hpp" #include "../fast_library.hpp" #include "../libpatricia/patricia.hpp" #include "../all_logcpp_libraries.hpp" using json = nlohmann::json; #ifdef __MACH__ // On MacOS X we haven't clock_gettime(CLOCK_REALTIME, &ts) and should use another code // http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x #include #include #define CLOCK_REALTIME 1111 clock_serv_t cclock; // Create custom wrapper for Mac OS X int clock_gettime(int clodk_type_do_not_used_really, struct timespec* ts) { mach_timespec_t mts; clock_get_time(cclock, &mts); ts->tv_sec = mts.tv_sec; ts->tv_nsec = mts.tv_nsec; return 0; } #endif log4cpp::Category& logger = log4cpp::Category::getRoot(); int main() { #ifdef __MACH__ host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); #endif patricia_tree_t* lookup_tree; lookup_tree = New_Patricia(32); std::string line; std::ifstream myfile("/home/odintsov/isp_prefixes.txt"); if (!myfile.is_open()) { std::cerr << "Could not open file with prefix list" << std::endl; return 1; } std::cout << "Start subnet load to patricia" << std::endl; while (getline(myfile, line)) { // std::cout << "Add subnet " << line << " to patricia tree" << std::endl; make_and_lookup(lookup_tree, (char*)line.c_str()); } std::cout << "Finished subnet load to patricia" << std::endl; // Load example traffic std::ifstream example_traffic("/home/odintsov/isp_traffic.json"); if (!example_traffic.is_open()) { std::cerr << "Could not open file with example traffic" << std::endl; return 1; } std::vector> fragmented_vector_of_packets; std::cout << "Start loading traffic into memory" << std::endl; while (getline(example_traffic, line)) { auto json_conf = json::parse(line, nullptr, false); if (json_conf.is_discarded()) { std::cerr << "Could not parse JSON: " << line << std::endl; return 1; } // We test only IPv4 for now if (json_conf["ip_version"].get() != "ipv4") { continue; } uint32_t src_ip = 0; uint32_t dst_ip = 0; bool source_res = convert_ip_as_string_to_uint_safe(json_conf["source_ip"].get(), src_ip); if (!source_res) { std::cout << "Cannot parse src ip" << std::endl; continue; } bool destionation_res = convert_ip_as_string_to_uint_safe(json_conf["destination_ip"].get(), dst_ip); if (!destionation_res) { std::cout << "Cannot parse dst ip" << std::endl; continue; } // std::cout << json_conf["source_ip"].get() << " " << json_conf["destination_ip"].get() << std::endl; fragmented_vector_of_packets.push_back(std::make_pair(src_ip, dst_ip)); } std::cout << "Loaded traffic into memory" << std::endl; std::cout << "Defragment memory for input packet set" << std::endl; // Copy traffic into single continious memory regiuon to avoid issues performance issues due to memory frragmentation std::vector> vector_of_packets; vector_of_packets.reserve(fragmented_vector_of_packets.size()); for (const auto& pair : fragmented_vector_of_packets) { vector_of_packets.push_back(pair); } fragmented_vector_of_packets.clear(); std::cout << "Defragmentation done" << std::endl; std::cout << "I have " << vector_of_packets.size() << " real packets for test" << std::endl; std::cout << "Start tests" << std::endl; // Process vector_of_packets prefix_t prefix_for_check_adreess; prefix_for_check_adreess.family = AF_INET; prefix_for_check_adreess.bitlen = 32; // prefix_for_check_adreess.add.sin.s_addr = 123123123; struct timespec start_time; clock_gettime(CLOCK_REALTIME, &start_time); unsigned long total_ops = 0; if (false) { int i_iter = 100; // Million operations int j_iter = 1000000; total_ops = i_iter * j_iter; uint64_t matches = 0; for (int j = 0; j < j_iter; j++) { for (int i = 0; i < i_iter; i++) { // Random Pseudo IP prefix_for_check_adreess.add.sin.s_addr = i * j; patricia_node_t* found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); if (found_patrica_node != NULL) { matches++; } } } } uint64_t number_of_reruns = 1000; // I do not multiple by two here becasue we assume that interation involves two lookups all the time total_ops = number_of_reruns * vector_of_packets.size(); uint64_t match_source = 0; uint64_t match_destionation = 0; for (int j = 0; j < number_of_reruns; j++) { for (const auto& pair : vector_of_packets) { prefix_for_check_adreess.add.sin.s_addr = pair.first; patricia_node_t* found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); if (found_patrica_node != NULL) { match_source++; } // Repeat for another IP prefix_for_check_adreess.add.sin.s_addr = pair.second; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); if (found_patrica_node != NULL) { match_destionation++; } } } struct timespec finish_time; clock_gettime(CLOCK_REALTIME, &finish_time); std::cout << "match_source: " << match_source << " match_destionation: " << match_destionation << std::endl; unsigned long used_seconds = finish_time.tv_sec - start_time.tv_sec; unsigned long used_nanoseconds = finish_time.tv_nsec - start_time.tv_nsec; unsigned long total_used_nanoseconds = used_seconds * 1000000000 + used_nanoseconds; float megaops_per_second = (float)total_ops / ((float)total_used_nanoseconds / (float)1000000000) / 1000000; std::cout << "Total time is " << used_seconds << " seconds total ops: " << total_ops << std::endl; std::cout << "Million of ops per second: " << megaops_per_second << std::endl; Destroy_Patricia(lookup_tree, [](void* ptr) {}); #ifdef __MACH__ mach_port_deallocate(mach_task_self(), cclock); #endif } upstream-fastnetmon/src/tests/sort_struct.cpp0000644000175000017500000000614714230006537017716 0ustar meme#include #include #include #include #include #include bool compare_min(unsigned int a, unsigned int b) { return a > b; } bool compare_max(unsigned int a, unsigned int b) { return a < b; } template class fast_priority_queue { public: fast_priority_queue(unsigned int queue_size) { this->queue_size = queue_size; internal_list.reserve(queue_size); } void insert(order_by_template_type main_value, int data) { // Because it's ehap we can remove // Append new element to the end of list internal_list.push_back(main_value); // Convert list to the complete heap // Up to logarithmic in the distance between first and last: Compares elements and // potentially swaps (or moves) them until rearranged as a longer heap. std::push_heap(internal_list.begin(), internal_list.end(), compare_min); if (this->internal_list.size() >= queue_size) { // And now we should remove minimal element from the internal_list // Prepare heap to remove min element std::pop_heap(internal_list.begin(), internal_list.end(), compare_min); // Remove element from the head internal_list.pop_back(); } } order_by_template_type get_min_element() { // We will return head of list because it's consists minimum element return internal_list.front(); } void print_internal_list() { for (unsigned int i = 0; i < internal_list.size(); i++) { std::cout << internal_list[i] << std::endl; } } void print() { // Create new list for sort because we can't do it in place std::vector sorted_list; // Allocate enough space sorted_list.reserve(internal_list.size()); // Copy to new vector with copy constructor sorted_list = internal_list; // Execute heap sort because array paritally sorted already std::sort_heap(sorted_list.begin(), sorted_list.end(), compare_min); for (unsigned int i = 0; i < sorted_list.size(); i++) { std::cout << sorted_list[i] << std::endl; } } private: order_by_template_type max_number; order_by_template_type min_number; unsigned int queue_size; // We can't use list here! std::vector internal_list; // std::priority_queue, std::less > class_priority_queue; }; int main() { fast_priority_queue my_priority_queue(10); for (int i = 0; i < 100; i++) { int current_value = rand() % 100; // std::cout< my_priority_queue.get_min_element()) { // Check for existence of this element in struct already my_priority_queue.insert(current_value, 0); } } std::cout << "Print internal list" << std::endl; my_priority_queue.print_internal_list(); std::cout << "Print sorted list" << std::endl; my_priority_queue.print(); } upstream-fastnetmon/src/tests/spsc_prototype.cpp0000664000175000017500000000721615060514305020417 0ustar meme#include #include #include #include #include #include #include #include #include #include #include #include "../fast_library.h" #include "../fastnetmon_types.h" #include "../libpatricia/patricia.hpp" #include "../netflow_plugin/netflow_collector.h" #include "../pcap_plugin/pcap_collector.h" #include "../sflow_plugin/sflow_collector.h" #include "../netmap_plugin/netmap_collector.h" #include #include #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" #include #include #include #include #include using namespace std; typedef simple_packet* simple_packet_shared_ptr_t; typedef boost::lockfree::spsc_queue> my_spsc_queue_t; uint64_t total_unparsed_packets = 0; my_spsc_queue_t my_spsc_queue[8]; std::string log_file_path = "/tmp/fastnetmon_plugin_tester.log"; log4cpp::Category& logger = log4cpp::Category::getRoot(); #include extern boost::pool_allocator alloc[8]; // #define DO_SUBNET_LOOKUP #ifdef DO_SUBNET_LOOKUP patricia_tree_t* lookup_tree; #endif // Global map with parsed config file std::map configuration_map; void init_logging() { log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("%d [%p] %m%n"); log4cpp::Appender* appender = new log4cpp::FileAppender("default", log_file_path); appender->setLayout(layout); logger.setPriority(log4cpp::Priority::INFO); logger.addAppender(appender); logger.info("Logger initialized"); } uint64_t received_packets = 0; void process_packet(simple_packet& current_packet) { //__sync_fetch_and_add(&received_packets, 1); // std::cout << print_simple_packet(current_packet); } std::unordered_map map_counter; void traffic_processor() { simple_packet_shared_ptr_t packet; // map_counter.reserve(16000000); while (1) { for (int i = 0; i < 8; i++) { // while (!my_spsc_queue[thread_number].push(packet)); while (my_spsc_queue[i].pop(packet)) { // std::cout << print_simple_packet(packet); // map_counter[packet.src_ip]++; __sync_fetch_and_add(&received_packets, 1); delete packet; // alloc[i].deallocate(packet, 1); } } } } void speed_printer() { while (true) { uint64_t packets_before = received_packets; boost::this_thread::sleep(boost::posix_time::seconds(1)); uint64_t packets_after = received_packets; uint64_t pps = packets_after - packets_before; printf("We process: %llu pps\n", pps); } } int main(int argc, char* argv[]) { boost::thread speed_printer_thread(speed_printer); boost::thread traffic_processor_thread(traffic_processor); init_logging(); // Required by Netmap plugin // We use fake interface name here because netmap could make server unreachable :) configuration_map["interfaces"] = "eth5"; start_netmap_collection(process_packet); traffic_processor_thread.join(); speed_printer_thread.join(); } upstream-fastnetmon/src/tests/test_cidr.cpp0000644000175000017500000000267614232165256017314 0ustar meme#include #include #include #include #include #include #include #include uint32_t convert_cidr_to_binary_netmask(unsigned int cidr) { uint32_t binary_netmask = 0xFFFFFFFF; binary_netmask = binary_netmask << (32 - cidr); // htonl from host byte order to network // ntohl from network byte order to host // We need network byte order at output return htonl(binary_netmask); } uint32_t convert_ip_as_string_to_uint(std::string ip) { struct in_addr ip_addr; inet_aton(ip.c_str(), &ip_addr); // in network byte order return ip_addr.s_addr; } int main() { uint32_t network_zero = convert_ip_as_string_to_uint("10.10.10.0"); uint32_t network_200 = convert_ip_as_string_to_uint("10.10.10.200"); uint32_t binary_netmask = convert_cidr_to_binary_netmask(24); uint32_t generated_subnet_address = network_200 & binary_netmask; std::cout << "network byte order" << std::endl; std::cout << "10.10.10.200/24\tnetwork byte order:" << network_200 << " host byte order:" << ntohl(network_200) << std::endl; std::cout << "10.10.10.0/24\tnetwork byte order:" << network_zero << " host byte order:" << ntohl(network_zero) << std::endl; std::cout << "generated \tnetwork byte order:" << generated_subnet_address << " host byte order:" << ntohl(generated_subnet_address) << std::endl; } upstream-fastnetmon/src/tests/tsc_timers.cpp0000644000175000017500000000341614232165256017501 0ustar meme#include #include #include #include #include #include inline uint64_t read_tsc_cpu_register(void) { union { uint64_t tsc_64; struct { uint32_t lo_32; uint32_t hi_32; }; } tsc; asm volatile("rdtsc" : "=a"(tsc.lo_32), "=d"(tsc.hi_32)); return tsc.tsc_64; } uint64_t get_tsc_freq_with_sleep() { uint64_t start = read_tsc_cpu_register(); sleep(1); return read_tsc_cpu_register() - start; } uint64_t get_tsc_freq_from_clock(void) { //#ifdef CLOCK_MONOTONIC_RAW #define NS_PER_SEC 1E9 struct timespec sleeptime; sleeptime.tv_sec = 1; sleeptime.tv_nsec = 5E8; /* 1/2 second */ struct timespec t_start, t_end; if (clock_gettime(CLOCK_MONOTONIC_RAW, &t_start) == 0) { uint64_t ns, end, start; start = read_tsc_cpu_register(); nanosleep(&sleeptime, NULL); clock_gettime(CLOCK_MONOTONIC_RAW, &t_end); end = read_tsc_cpu_register(); ns = ((t_end.tv_sec - t_start.tv_sec) * NS_PER_SEC); ns += (t_end.tv_nsec - t_start.tv_nsec); double secs = (double)ns / NS_PER_SEC; return (uint64_t)((end - start) / secs); } //#endif } int main() { /* The frequency of the RDTSC timer resolution */ uint64_t fastnetmon_tsc_resolution_hz = 0; printf("Determine TSC freq with sleep\n"); fastnetmon_tsc_resolution_hz = get_tsc_freq_with_sleep(); printf("TSC freq is %llu\n", fastnetmon_tsc_resolution_hz); printf("Determing TSC freq with CLOCK_MONOTONIC_RAW\n"); fastnetmon_tsc_resolution_hz = get_tsc_freq_from_clock(); printf("TSC freq is %llu\n", fastnetmon_tsc_resolution_hz); printf("Current TSC value %llu\n", read_tsc_cpu_register()); } upstream-fastnetmon/src/tests/traffic_structures_performance_tests_real_traffic.cpp0000664000175000017500000002605715060514305027553 0ustar meme#include #include #include #include #include #include #include #include #include #include #include #include #include "../all_logcpp_libraries.hpp" #include "../fast_library.hpp" #include "../fast_endianless.hpp" #include "../fastnetmon_types.hpp" #include #include #include #include #ifdef ABSEIL_TESTS #include "absl/container/flat_hash_map.h" #include "absl/container/node_hash_map.h" #endif #ifdef __MACH__ // On MacOS X we haven't clock_gettime(CLOCK_REALTIME, &ts) and should use another code // http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x #include #include #define CLOCK_REALTIME 1111 clock_serv_t cclock; // Create custom wrapper for Mac OS X int clock_gettime(int clodk_type_do_not_used_really, struct timespec* ts) { mach_timespec_t mts; clock_get_time(cclock, &mts); ts->tv_sec = mts.tv_sec; ts->tv_nsec = mts.tv_nsec; return 0; } #endif log4cpp::Category& logger = log4cpp::Category::getRoot(); // We use these structures to tests smaller value class udp_t { public: uint64_t in_bytes = 0; }; class subnet_counter_small_t { public: udp_t udp; }; // Runs tests for specific structure template void run_tests(std::vector our_ips, T& data_structure) { struct timespec start_time; clock_gettime(CLOCK_REALTIME, &start_time); unsigned long total_ops = 0; uint64_t number_of_reruns = 1000; total_ops = number_of_reruns * our_ips.size(); for (int j = 0; j < number_of_reruns; j++) { for (const auto& ip : our_ips) { data_structure[ip].udp.in_bytes++; } } struct timespec finish_time; clock_gettime(CLOCK_REALTIME, &finish_time); unsigned long used_seconds = finish_time.tv_sec - start_time.tv_sec; unsigned long used_nanoseconds = finish_time.tv_nsec - start_time.tv_nsec; unsigned long total_used_nanoseconds = used_seconds * 1000000000 + used_nanoseconds; float megaops_per_second = (float)total_ops / ((float)total_used_nanoseconds / (float)1000000000) / 1000000; std::cout << "Total lookup time is " << used_seconds << " seconds" << std::endl; std::cout << "Million of lookup ops per second: " << megaops_per_second << std::endl; #ifdef __MACH__ mach_port_deallocate(mach_task_self(), cclock); #endif } // Runs tests for specific structure template void run_scan_tests(T& data_structure, uint64_t& accumulator) { struct timespec start_time; clock_gettime(CLOCK_REALTIME, &start_time); unsigned long total_ops = 0; uint64_t number_of_reruns = 1000; total_ops = number_of_reruns * data_structure.size(); for (int j = 0; j < number_of_reruns; j++) { for (const auto& elem : data_structure) { accumulator += elem.second.udp.in_bytes; } } struct timespec finish_time; clock_gettime(CLOCK_REALTIME, &finish_time); unsigned long used_seconds = finish_time.tv_sec - start_time.tv_sec; unsigned long used_nanoseconds = finish_time.tv_nsec - start_time.tv_nsec; unsigned long total_used_nanoseconds = used_seconds * 1000000000 + used_nanoseconds; float megaops_per_second = (float)total_ops / ((float)total_used_nanoseconds / (float)1000000000) / 1000000; std::cout << "Total scan time is " << used_seconds << " seconds" << std::endl; std::cout << "Million of full scan ops per second: " << megaops_per_second << std::endl; #ifdef __MACH__ mach_port_deallocate(mach_task_self(), cclock); #endif } int main() { #ifdef __MACH__ host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); #endif std::string file_path = "/home/odintsov/cable_isp_ip_addresses_non_unique.txt"; std::string line; std::ifstream myfile(file_path); if (!myfile.is_open()) { std::cerr << "Could not open file with IP list: " << file_path << std::endl; return 1; } std::vector our_ips_big_endian; std::vector our_ips_little_endian; // We know file size and allocate that number of elements + some spare space our_ips_big_endian.reserve(200000); our_ips_little_endian.reserve(200000); while (getline(myfile, line)) { // It will be big endian internally uint32_t ip = 0; bool res = convert_ip_as_string_to_uint_safe(line, ip); if (!res) { std::cout << "Cannot parse IP " << line << std::endl; continue; } our_ips_big_endian.push_back(ip); // Convert it to little endian our_ips_little_endian.push_back(fast_ntoh(ip)); } std::cout << "Loaded " << our_ips_big_endian.size() << " IPs into memory" << std::endl; { uint64_t accumulator = 0; std::cout << std::endl << "std::map big endian " << std::endl; std::map std_map; run_tests(our_ips_big_endian, std_map); run_scan_tests(std_map, accumulator); std_map.clear(); std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "std::map little endian " << std::endl; std::map std_map; run_tests(our_ips_little_endian, std_map); run_scan_tests(std_map, accumulator); std_map.clear(); std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "std::unordered_map big endian" << std::endl; std::unordered_map std_unordered; run_tests(our_ips_big_endian, std_unordered); run_scan_tests(std_unordered, accumulator); std_unordered.clear(); std::cout << "Bucket number: " << std_unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "std::unordered_map little endian" << std::endl; std::unordered_map std_unordered; run_tests(our_ips_little_endian, std_unordered); run_scan_tests(std_unordered, accumulator); std_unordered.clear(); std::cout << "Bucket number: " << std_unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "boost::unordered_map big endian " << std::endl; boost::unordered_map boost_unordered; run_tests(our_ips_big_endian, boost_unordered); run_scan_tests(boost_unordered, accumulator); boost_unordered.clear(); std::cout << "Bucket number: " << boost_unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "boost::unordered_map little endian " << std::endl; boost::unordered_map boost_unordered; run_tests(our_ips_little_endian, boost_unordered); run_scan_tests(boost_unordered, accumulator); boost_unordered.clear(); std::cout << "Bucket number: " << boost_unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "boost::unordered_flat_map big endian " << std::endl; boost::unordered_flat_map boost_unordered; run_tests(our_ips_big_endian, boost_unordered); run_scan_tests(boost_unordered, accumulator); boost_unordered.clear(); std::cout << "Bucket number: " << boost_unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "boost::unordered_flat_map little endian " << std::endl; boost::unordered_flat_map boost_unordered; run_tests(our_ips_little_endian, boost_unordered); run_scan_tests(boost_unordered, accumulator); boost_unordered.clear(); std::cout << "Bucket number: " << boost_unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } #ifdef ABSEIL_TESTS { uint64_t accumulator = 0; std::cout << std::endl << "absl::flat_hash_map little endian " << std::endl; absl::flat_hash_map unordered; run_tests(our_ips_little_endian, unordered); run_scan_tests(unordered, accumulator); unordered.clear(); std::cout << "Bucket number: " << unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "absl::flat_hash_map big endian " << std::endl; absl::flat_hash_map unordered; run_tests(our_ips_big_endian, unordered); run_scan_tests(unordered, accumulator); unordered.clear(); std::cout << "Bucket number: " << unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "absl::node_hash_map little endian " << std::endl; absl::node_hash_map unordered; run_tests(our_ips_little_endian, unordered); run_scan_tests(unordered, accumulator); unordered.clear(); std::cout << "Bucket number: " << unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } { uint64_t accumulator = 0; std::cout << std::endl << "node_hash_map big endian " << std::endl; absl::node_hash_map unordered; run_tests(our_ips_big_endian, unordered); run_scan_tests(unordered, accumulator); unordered.clear(); std::cout << "Bucket number: " << unordered.bucket_count() << std::endl; std::cout << "Accumulator value to guarantee no optimisation tricks from compiler: " << accumulator << std::endl; } #endif } upstream-fastnetmon/src/tests/fuzz/0000775000175000017500000000000015060514305015606 5ustar memeupstream-fastnetmon/src/tests/fuzz/README.md0000664000175000017500000003246415060514305017076 0ustar meme# Fuzzing This section describes the fuzzing testing process, the approaches used, and methods applied, including both successful and unsuccessful attempts. ## Navigation -------------------------------- - [Docker Image](#docker-image) - [CMake](#cmake) - [File Structure](#file-structure) - [Example of Fuzzing Run](#example-of-manual-fuzzing-launch-for-individual-fuzzing-wrappers) - [Other Fuzzing Techniques](#other-fuzzing-techniques) - [Fuzzing Launch in Docker Container](#fuzzing-launch-in-docker-container) ## Docker Image -------------------------------- The script is based on `tests/Dockerfile.ubuntu-24.04-afl++`. This image builds and installs everything necessary for further testing: | Module | Description | |------------------------------------------|-----------------------------------------------------------| | [`AFL++`](https://github.com/AFLplusplus) | Fuzzer | | [`casr`](https://github.com/ispras/casr) | Utility for crash verification, minimization, and clustering. Requires rust | | [`desock`](https://github.com/zardus/preeny/) | Utility for replacing system calls. Used in the project to replace the `socket` function, which takes data from the interface, with a function that takes data from the console | Sanitizers (ASAN, UBSAN, etc.) are also used in the project for runtime error detection. ### Build Docker Image -------------------------------- To build the Docker image for testing, run the following command from the root directory `fastnetmon`: ```bash docker build -f tests/Dockerfile.ubuntu-24.04-afl++ . -t fuzz ``` After the build is complete, an `image` named `fuzz` will be created. ## CMake -------------------------------- A number of options have been added to the source `CMakeLists.txt` file, allowing the building of separate fuzzing wrappers using different cmake options. *The options in the table will be listed with the `-D` prefix, which allows setting the option as an argument to the cmake utility when run from the command line.* | Option | Description | |-----------------------------------|-----------------------------------------------------------| | `-DENABLE_FUZZ_TEST` | Builds two fuzzing wrappers for `AFL++`. Use **only with the `afl-c++` compiler** or its variations | | `-DENABLE_FUZZ_TEST_DESOCK` | This option allows modifying the behavior of the standard `socket` function. Now data will come from the input stream instead of the network socket. **Instruments the original `fastnetmon` executable** | | `-DCMAKE_BUILD_TYPE=Debug` | Debugging option required for proper debugger functionality. **Do not use on release builds or during tests - false positives may occur with sanitizer functions like `assert()`** | ## File Structure -------------------------------- ``` fuzz/ ├── README.md ├── README_rus.md ├── fastnetmon.conf ├── parse_sflow_v5_packet_fuzz.cpp ├── process_netflow_packet_v5_fuzz.cpp └── scripts/ │ ├── minimize_out.sh │ ├── afl_pers_mod_instr.sh │ ├── start_fuzz_conf_file.sh │ └── start_fuzz_harness.sh ``` ### File Descriptions -------------------------------- | File | Description | |-----------------------------------------|-----------------------------------------------------------------------------------------------| | `README.md` | Documentation in **English** about the fuzz testing of the project. | | `README_rus.md` | Documentation in **Russian** about the fuzz testing of the project. | | `fastnetmon.conf` | Configuration file for FastNetMon. Only the netflow and sflow protocols are left for operation. | | `parse_sflow_v5_packet_fuzz.cpp` | Wrapper for fuzzing the `parse_sflow_v5_packet_fuzz` function using `AFL++`. | | `process_netflow_packet_v5_fuzz.cpp` | Wrapper for fuzzing the `process_netflow_packet_v5_fuzz` function using `AFL++`. | | File/Directory | Description | Run | |----------------------------------------|-------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------| | `/scripts/` | Directory containing scripts for automating fuzzing. | | | `/scripts/minimize_out.sh` | Script for minimizing, verifying, and clustering crash outputs. | `./minimize_out.sh <./binary>` | | `/scripts/start_fuzz_conf_file.sh` | Script for running fuzzing on configuration files. Launches several tmux sessions. Uses options `./fastnetmon --configuration_check --configuration_file`. | Run from the current directory without additional options. The environment is automatically set up. | | `/scripts/start_fuzz_harness.sh` | Script for testing binary files compiled from wrappers into separate executables. Designed for wrappers compiled for `AFL++`. It sets up the environment, creates folders, and launches two tmux sessions with fuzzer instances. After fuzzing ends, it runs the `minimize_out.sh` script for crash clustering. | `./start_fuzz_harness.sh ` The script will stop if no new paths are found within a certain time. By default, the time is 1 hour. To change it, modify the `TIME` variable (in seconds) inside the script. | | `/scripts/afl_pers_mod_instr.sh` | A script that adds `AFL++` instrumentation for fuzzing in `persistent mode`. **Important! Currently used only with `netflow_collector.cpp` and `sflow_collector.cpp`** | `./afl_pers_mod_instr.sh ` | ## Example of manual fuzzing launch for individual fuzzing wrappers -------------------------------- Run the container: ```bash docker run --privileged -it fuzz /bin/bash ``` To run AFL++ fuzzing with multi-threading enabled: ```bash echo core | tee /proc/sys/kernel/core_pattern ``` **Don't forget to collect the data corpus in the in folder** For a test run, use a single sed with a '1': ```bash mkdir in echo "1" >> in/1 ``` With a standard docker image build, there will be a folder build_fuzz_harness where the following fuzzing wrappers will be compiled: - `parse_sflow_v5_packet_fuzz` - `process_netflow_packet_v5_fuzz` **Start fuzzing:** ```bash afl-fuzz -i in -o out -- ./parse_sflow_v5_packet_fuzz ``` Or ```bash afl-fuzz -i in -o out -- ./process_netflow_packet_v5_fuzz ``` - The `build_netflow_pers_mod` folder will contain the code for fuzzing the `process_netflow_packet_v5` function via `AFL++ persistent mode`. - The `build_sflow_pers_mod` folder will contain the code for fuzzing the `parse_sflow_v5_packet` function via `AFL++ persistent mode`. If the build is done manually, use the `afl_pers_mod_instr.sh` script to instrument the files. The fuzzing launch for these functions is the same, as the final executable file fastnetmon is instrumented: ```bash afl-fuzz -i in -o out -- ./fastnetmon ``` **IMPORTANT!** For multi-thread fuzzing of the fastnetmon file, you need to provide a separate configuration file for each instance of the fuzzer for `fastnetmon`, specifying different ports for protocols, otherwise, the instances will conflict, and multiple threads will not be able to run. ## Example of fuzzing launch via automation script -------------------------------- *All actions take place inside the container, where the working directory is `src`, so paths are constructed relative to this folder.* For fuzzing, we use the `start_fuzz_harness` script. For wrappers compiled into binary files: ```bash /src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_fuzz_harness/process_netflow_packet_v5_fuzz ``` Or ```bash /src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_fuzz_harness/parse_sflow_v5_packet_fuzz ``` For instrumenting `fastnetmon`: ```bash /src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_netflow_pers_mod/fastnetmon ``` Or ```bash /src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_sflow_pers_mod/fastnetmon ``` After launching, a directory `_fuzz_dir` will be created, inside which a folder `input` will be generated. A folder `/output` will be created at the root of the system, where fuzzing output files and clustering files will be sent. This is necessary to easily access data after fuzzing inside the container (see below). A `tmux` session with an AFL++ fuzzer instance will be started. Fuzzing will continue until no new paths are found within an hour (this value can be changed inside the script). Then, the tmux session will end, and clustering and crash checking will begin using the `minimize_out.sh` script. ## Other Fuzzing Techniques ### Coarse Code Intervention Using Persistent Mode AFL++ -------------------------------- The fuzzer `AFL++` allows for rewriting parts of the code for fuzzing, significantly increasing the fuzzing speed (the program does not terminate after processing one data set, but instead restarts the cycle with the target function multiple times). This approach can be used to instrument two different targets: - `src/netflow_plugin/netflow_collector.cpp : start_netflow_collector(...)` - `src/sflow_plugin/sflow_collector.cpp : start_sflow_collector(...)` How the instrumentation looks: 1. Add the construct `__AFL_FUZZ_INIT();` before the target function. 2. Replace the `while (true)` loop with `while (__AFL_LOOP(10000))`. 3. Replace `char udp_buffer[udp_buffer_size];` with `unsigned char * udp_buffer = __AFL_FUZZ_TESTCASE_BUF;`. 4. Replace `int received_bytes = recvfrom(sockfd, udp_buffer, udp_buffer_size, 0, (struct sockaddr*)&client_addr, &address_len);` with `int received_bytes = __AFL_FUZZ_TESTCASE_LEN;`. 5. Build with the AFL++ compiler and sanitizers. No wrappers are needed for compilation. 6. Run fuzzing with: `afl-fuzz -i in -o out -- ./fastnetmon` For these two purposes, an automation script for code instrumentation has been created. See more details in the script `/scripts/afl_pers_mod_instr.sh`. ### Other Fuzzing Techniques -------------------------------- | Name | Description | |---------------------|------------------------------------------------------------------------------------------------------| | `AFLNet` | The characteristics of the protocol (lack of feedback) prevent the use of this fuzzer. | | `desock` | Code instrumentation is successful, but the fuzzer does not start and cannot collect feedback. I consider this method **promising**, but the fuzzer requires adjustments. | | `libfuzzer` | Wrappers for `libfuzzer` were written and implemented into cmake, but due to the peculiarities of the build and the project's focus on fuzzing via `AFL++`, they were removed from the project. Commit with a working [`libfuzzer`] wrappers (https://github.com/pavel-odintsov/fastnetmon/commit/c3b72c18f0bc7f43b535a5da015c3954d716be22) ## Fuzzing Launch in Docker Container ### A Few Words for Context Each fuzzer thread requires one system thread. The `start_fuzz_harness.sh` script includes a time limit for fuzzing. The `TIME` variable is responsible for the "last path found" time parameter. If this parameter stops resetting, it means the fuzzer has hit a deadlock and there's no point in continuing fuzzing. From empirical experience, this parameter should be set to 2 hours. The project has it set to 1 hour. If shallow testing is needed, this parameter can be reduced to 10-15 minutes, making the total fuzzing time last a few hours. ### Build and Launch Build: ```bash cd fastnetmon docker build -f tests/Dockerfile.ubuntu-24.04-afl++ -t fuzz . ``` Launch the container: ``` mkdir work docker run -v $(pwd)/work:/output --privileged -it fuzz /bin/bash -c "/src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_netflow_pers_mod/fastnetmon" ``` This method can be used to launch any wrapper / binary file by simply providing the command from the *Example of fuzzing launch via automation script* section in quotes after the `-c` argument. After fuzzing is completed, the results will be placed in the host system's work folder—both the results folder and the clustering folder will be there. The container will have a status exit. It can be manually restarted to check for crashes. upstream-fastnetmon/src/tests/fuzz/process_netflow_packet_v5_fuzz.cpp0000664000175000017500000000434215060514305024550 0ustar meme#include "../../abstract_subnet_counters.hpp" #include "../../fastnetmon_configuration_scheme.hpp" #include "../../bgp_protocol_flow_spec.hpp" #include "../../netflow_plugin/netflow_collector.hpp" #include "../../sflow_plugin/sflow_collector.hpp" #include "../../fastnetmon_logic.hpp" log4cpp::Category& logger = log4cpp::Category::getRoot(); time_t current_inaccurate_time = 0; fastnetmon_configuration_t fastnetmon_global_configuration; packet_buckets_storage_t packet_buckets_ipv6_storage; bool DEBUG_DUMP_ALL_PACKETS = false; bool DEBUG_DUMP_OTHER_PACKETS = false; uint64_t total_ipv6_packets = 0; uint64_t total_ipv4_packets = 0; patricia_tree_t *lookup_tree_ipv4; patricia_tree_t *lookup_tree_ipv6; uint64_t total_flowspec_whitelist_packets = 0; uint64_t total_simple_packets_processed = 0; uint64_t unknown_ip_version_packets = 0; bool process_incoming_traffic = true; bool process_outgoing_traffic = true; bool enable_connection_tracking = true; std::vector static_flowspec_based_whitelist; packet_buckets_storage_t packet_buckets_ipv4_storage; total_speed_counters_t total_counters_ipv4; total_speed_counters_t total_counters_ipv6; abstract_subnet_counters_t ipv6_network_counters; abstract_subnet_counters_t ipv6_host_counters; abstract_subnet_counters_t ipv4_network_counters; abstract_subnet_counters_t ipv4_host_counters; map_of_vector_counters_for_flow_t SubnetVectorMapFlow; std::mutex flow_counter_mutex; extern process_packet_pointer netflow_process_func_ptr = process_packet; __AFL_FUZZ_INIT(); int main(int argc, char** argv) { uint32_t client_ipv4_address = 128; std::string ip_add = "192.168.0.1"; unsigned char* udp_buffer = __AFL_FUZZ_TESTCASE_BUF; while (__AFL_LOOP(10000)) { unsigned int received_bytes = __AFL_FUZZ_TESTCASE_LEN; process_netflow_packet(udp_buffer, received_bytes, ip_add, client_ipv4_address); } return 0; } //afl-fuzz with export ASAN_OPTIONS=detect_odr_violation=0:abort_on_error=1:symbolize=0upstream-fastnetmon/src/tests/fuzz/fastnetmon.conf0000664000175000017500000002500315060514305020633 0ustar meme### ### 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 # 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 = on # sFLOW capture suitable for switches sflow = on # 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 = ens160 # 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 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 upstream-fastnetmon/src/tests/fuzz/README_rus.md0000664000175000017500000004601315060514305017762 0ustar meme# Fuzzing В этом разделе описан процесс фаззинг-тестирования, использованные подходы и методы, включая как успешные, так и неудачные попытки. ## Навигация -------------------------------- - [Docker Image](#docker-image) - [CMake](#cmake) - [Файловая структура](#файловая-структура) - [Пример запуска фаззинга](#пример-запуска-фаззинга-вручную-для-отдельных-фаззинг-оберток) - [Другие техники фаззинга](#другие-техники-фаззинга) - [Запуск фаззинга в контейнере](#запуск-фаззинга-в-docker-contianer) ## Docker Image -------------------------------- Скрипт базируется на `tests/Dockerfile.ubuntu-24.04-afl++`. В этом образе собирается и устанавливается все необходимое для дальнейшего тестирования: | Модуль | Описание | |---------------------------------------------|-------------------------------------------------------------| | [`AFL++`](https://github.com/AFLplusplus) | Фаззер | | [`casr`](https://github.com/ispras/casr) | Утилита для проверки, минимазации и кластеризации крашей. Нужен rust | | [`desock`](https://github.com/zardus/preeny/) | Утилита для подмены системных вызовов. Используется в проекте для замены вызова функции `socket`, которая принимает данные с интерфейса, на функцию, которая принимает данные из консоли | Так же в проекте используются санитайзеры (ASAN, UBSAN и др.) для поиска ошибок в динамике. ### Build Docker Image -------------------------------- Сборка Docker image для тестирования происходит из коренной директории `fastnetmon` и запускается командой: ```bash docker build -f tests/Dockerfile.ubuntu-24.04-afl++ . -t fuzz ``` По завершению сборки буден получен `image` с названием `fuzz` ## Cmake -------------------------------- В исходный файл `CmakeLists.txt` был добавлен ряд опций, которые позволяют собирать отдельные фаззинг-обертки используя различные опции cmake. *В таблице опции будут указываться с приставкой `-D`, которая позволяет задать опцию, как аргумент утилиты cmake при запуске из командой строки* | Опция | Описание | |------------------------------------|-------------------------------------------------------------| | `-DENABLE_FUZZ_TEST` | Собирает две фаззинг обертки под `AFL++`. Использовать **только с копилятором `afl-c++`** или его вариациями | | `-DENABLE_FUZZ_TEST_DESOCK` | Данная опция позволяет реализоват изменение поведения стандартной функции `socket`. Теперь данные будут браться не с сетевого сокета, из потока ввода. **Инструментирует оригинальный исполняемый файл `fastnetmon`** | | `-DCMAKE_BUILD_TYPE=Debug` | Отладочная опция, необходимая для корректной работы отладчиков. Внимание! **Не использовать на релизе и при тестах - будут ложные срабатывания санитайзера на таких функциях, как `assert()` ## Файловая структура -------------------------------- ``` fuzz/ ├── README.md ├── README_rus.md ├── fastnetmon.conf ├── parse_sflow_v5_packet_fuzz.cpp ├── process_netflow_packet_v5_fuzz.cpp └── scripts/ │ ├── minimize_out.sh │ ├── afl_pers_mod_instr.sh │ ├── start_fuzz_conf_file.sh │ └── start_fuzz_harness.sh ``` ### Описание файловов -------------------------------- | Файл | Описание | |-----------------------------------------|------------------------------------------------------------------------------------------| | `README.md` | Документация на **английском языке** о фаззинг-тестировании проекта. | | `README_rus.md` | Документация на **русском языке** о фаззинг-тестировании проекта. | | `fastnetmon.conf` | Конфигурационный файл для FastNetMon. Оставлены для работы только протоколы netflow и sflow | | `parse_sflow_v5_packet_fuzz.cpp` | Обертка для фаззинга функции `parse_sflow_v5_packet_fuzz` через `AFL++`. | | `process_netflow_packet_v5_fuzz.cpp` | Обертка для фаззинга функции `process_netflow_packet_v5_fuzz` через `AFL++`. | | Файл/Директория | Описание | Запуск | |-----------------------------------------|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------| | `/scripts/` | Директория со скриптами для автоматизации фаззинга. | | | `/scripts/minimize_out.sh` | Скрипт для минимизации, верификации и кластеризации выходных данных (падений). | `./minimize_out.sh <./binary>` | | `/scripts/start_fuzz_conf_file.sh` | Скрипт для запуска фаззинга конфигурационных файлов. Запускает несколько сессий tmux. Использует опции `./fastnetmon --configuration_check --configuration_file`. | Запускать из текущей директории без дополнительных опций. Окружение создаётся автоматически. | | `/scripts/start_fuzz_harness.sh` | Скрипт для тестирования бинарных файлов, скомпилированных из обёрток в отдельные исполняемые файлы. Предназначен для обёрток, скомпилированных под `AFL++`. При запуске настраивает окружение, создаёт папки, запускает две сессии tmux с экземплярами фаззера. После окончания фаззинга запускает скрипт `minimize_out.sh` для кластеризации крашей. | `./start_fuzz_harness.sh ` Скрипт завершится, если не будет найдено новых путей в течение определённого времени. По умолчанию время равно 1 часу. Для изменения изменить переменную `TIME` (в секундах) внутри скрипта. | | `/scripts/afl_pers_mod_instr.sh` | Скрипт, который добавляет инструментацию `AFL++` для фаззинга в `persistant mode` **Важно! Сейчас используется только с `netflow_collector.cpp и `sflow_collector.cpp` | `./afl_pers_mod_instr.sh ` | ## Пример запуска фаззинга вручную для отдельных фаззинг-оберток -------------------------------- Запускаем контейнер: ```bash docker run --privileged -it fuzz /bin/bash ``` Для запуска фаззинга AFL++ разрешим многопточную работу: ```bash echo core | tee /proc/sys/kernel/core_pattern ``` **Не забудьте собрать корпус данных в папку in** Для тестового запуска будем использовать один sed с единичкой: ```bash mkdir in echo "1" >> in/1 ``` При стандратной сборке `docker image` у нас будет папка `build_fuzz_harness`, внутри которой будт скомпилированы для фаззинг-обертки: - `parse_sflow_v5_packet_fuzz` - `process_netflow_packet_v5_fuzz` **Запустим фаззинг:** ```bash afl-fuzz -i in -o out -- ./parse_sflow_v5_packet_fuzz ``` Или ```bash afl-fuzz -i in -o out -- ./process_netflow_packet_v5_fuzz ``` - В папке `build_netflow_pers_mod` будет находится код, сделанный для фаззинга функции `process_netflow_packet_v5` через AFL++ `persistant mode`. - В папке `build_sflow_pers_mod` будет находится код, сделанный для фаззинга функции `parse_sflow_v5_packet` через AFL++ `persistant mode`. Если сборка делается вручную, то используйте скрипт `afl_pers_mod_instr.sh` для инструментации файлов. Запуск фаззинга этих функций одинаков, поскольку инструментируется финальный исполняемый файл `fastnetmon` ```bash afl-fuzz -i in -o out -- ./fastnetmon ``` **ВАЖНО!** Для фаззинга в многопотоке файла `fastnetmon` необходимо для каждого инстанса фаззера для `fastnetmon` подавать свой конфигурационный файл, где будут указаны разные порты для протоколов, иначе инстансы будут конфликтовать и несколько потоков не смогу запуститься. ## Пример запуска фаззинга через скрипт автоматизации -------------------------------- *Все действия происходят внутри контейнера, где рабочая директория `src`, поэтому пути строятся относительно данной папки* Для фаззинга их используем скрипт `start_fuzz_harness`. Для оберток скомпилированных в бинарные файлы: ```bash /src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_fuzz_harness/process_netflow_packet_v5_fuzz ``` Или ```bash /src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_fuzz_harness/parse_sflow_v5_packet_fuzz ``` Для инструментации `fastnetmon`: ```bash /src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_netflow_pers_mod/fastnetmon ``` Или ```bash /src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_sflow_pers_mod/fastnetmon ``` После запуска будет создана директория `_fuzz_dir`, внутри которой будут созданы папка `input`. В корне системы будет создана папка `/output`, в которую будут отправлены выходные файлы фаззинга и файлы кластеризации. Так нужно, чтобы было удобно получить данные после фаззинга в контейнере (Смотрите ниже). Будет запущена сессия `tmux` с инстансом фаззера `AFL++`. Фаззинг будет продолжаться, пока не будет прироста новых путей в течении часа (можно поменять значение внутри скрипта). Далее сессия tmux будет завершена, начнется кластеризация и проверка падений с помощью скрипта `minimize_out.sh` ## Другие техники фаззинга ### Грубое вмешательство в код с помощью Persistant Mode AFL++ -------------------------------- Фаззер `AFL++` позволяет переписать часть кода по фаззинг, попутно кратно увеличив скорость фаззинга (програма не заавершается после подачи одного набора даннх, а перезапускает цикл с функцией целью несколько раз). Таким способом можно инструментировать две разных цели: - `src/netflow_plugin/netflow_collector.cpp : start_netflow_collector(...)` - `src/sflow_plugin/sflow_collector.cpp : start_sflow_collector(...)` Как выглядит инструментация: 1. Перед целевой функций добавить конструкцию `__AFL_FUZZ_INIT();` 2. Цикл `while (true)` заменить на `while (__AFL_LOOP(10000))` 3. `char udp_buffer[udp_buffer_size];` заменить на `unsigned char * udp_buffer =__AFL_FUZZ_TESTCASE_BUF;` 4. `int received_bytes = recvfrom(sockfd, udp_buffer, udp_buffer_size, 0, (struct sockaddr*)&client_addr, &address_len);` заменить на `int received_bytes = __AFL_FUZZ_TESTCASE_LEN;` 5. Собрать с компилятором `AFL++` и санитайзерами. Собирать какие-либо обертки не нужно. 6. Запустить фаззинг `afl-fuzz -i in -o out -- ./fastnetmon` Для этих двух целей сделан скрипт автоматизации внедрения инструментации кода. Смотрите подрбобнее скрипт `/scripts/afl_pers_mod_instr.sh`. ### Иные технинки фаззинга -------------------------------- | Название | Описание | |---------------------|------------------------------------------------------------------------------------------| | `AFLNet` | Особенности протокола (отсутствие обратной связи) не дают использовать данный фаззер | | `desock` | Инструментация кода успешна, но фаззер не запускается - так же не может собрать обратную связь. Считаю данный способ **перспективным**, необходима корректировка фаззера. | | `libfuzzer` | Были написаны обертки под `libfuzzer` и внедрены в cmake, но из-за особенностей сборки и ориентации проекта на фаззинг через `AFL++` были убраны из проекта. Коммит с работающим [`libfuzzer`](https://github.com/pavel-odintsov/fastnetmon/commit/c3b72c18f0bc7f43b535a5da015c3954d716be22) ## Запуск фаззинга в docker contianer ### Пара слов для контекста На каждый поток фаззера нужен один системный поток. В скрипте `start_fuzz_harness.sh` есть время ограничения фаззинга. Переменная TIME отвечает за параметр "вермя нахождения последнего пути". Если этот параметр перестает обнуляться, значит фаззер заходит в тупик и нет смысла продолжать фаззинг. Из эмпирического опыта такой параметр нужно ставить на 2 часа. В проекте стоит на 1 час. Если необходимо выполнить поверхностное тестирование, то можно сократить этот параметр до 10 - 15 минут, тогда общее время фаззинга будет занимать несколько часов. ### Сборка и запуск Сборка ```bash cd fastnetmon docker build -f tests/Dockerfile.ubuntu-24.04-afl++ -t fuzz . ``` Запуск контейнера ``` mkdir work docker run -v $(pwd)/work:/output --privileged -it fuzz /bin/bash -c "/src/tests/fuzz/scripts/start_fuzz_harness.sh ./build_netflow_pers_mod/fastnetmon" ``` Таким способом можно запустить любую обертку / бинарный файл, просто подав команду из пункта *Пример запуска фаззинга через скрипт автоматизации* в кавычках после аргумента `-c` После окончая фаззинга результаты будут помещены на хостовую систему в папку `work` - там будет как папка с результатами, так и папка для кластеризации. Контейнер будет иметь `status exit`, его можно перезапустить вручную и проверить краши. upstream-fastnetmon/src/tests/fuzz/parse_sflow_v5_packet_fuzz.cpp0000664000175000017500000000422115060514305023654 0ustar meme#include "../../abstract_subnet_counters.hpp" #include "../../fastnetmon_configuration_scheme.hpp" #include "../../bgp_protocol_flow_spec.hpp" #include "../../netflow_plugin/netflow_collector.hpp" #include "../../sflow_plugin/sflow_collector.hpp" #include "../../fastnetmon_logic.hpp" log4cpp::Category& logger = log4cpp::Category::getRoot(); time_t current_inaccurate_time = 0; fastnetmon_configuration_t fastnetmon_global_configuration; packet_buckets_storage_t packet_buckets_ipv6_storage; bool DEBUG_DUMP_ALL_PACKETS = false; bool DEBUG_DUMP_OTHER_PACKETS = false; uint64_t total_ipv6_packets = 0; uint64_t total_ipv4_packets = 0; patricia_tree_t *lookup_tree_ipv4; patricia_tree_t *lookup_tree_ipv6; uint64_t total_flowspec_whitelist_packets = 0; uint64_t total_simple_packets_processed = 0; uint64_t unknown_ip_version_packets = 0; bool process_incoming_traffic = true; bool process_outgoing_traffic = true; bool enable_connection_tracking = true; std::vector static_flowspec_based_whitelist; packet_buckets_storage_t packet_buckets_ipv4_storage; total_speed_counters_t total_counters_ipv4; total_speed_counters_t total_counters_ipv6; abstract_subnet_counters_t ipv6_network_counters; abstract_subnet_counters_t ipv6_host_counters; abstract_subnet_counters_t ipv4_network_counters; abstract_subnet_counters_t ipv4_host_counters; map_of_vector_counters_for_flow_t SubnetVectorMapFlow; std::mutex flow_counter_mutex; extern process_packet_pointer sflow_process_func_ptr = process_packet; __AFL_FUZZ_INIT(); int main(int argc, char** argv) { uint32_t client_ipv4_address = 128; std::string ip_add = "192.168.0.1"; unsigned char* udp_buffer = __AFL_FUZZ_TESTCASE_BUF; while (__AFL_LOOP(10000)) { unsigned int received_bytes = __AFL_FUZZ_TESTCASE_LEN; parse_sflow_v5_packet( (uint8_t*)udp_buffer, received_bytes, client_ipv4_address); } return 0; }upstream-fastnetmon/src/tests/fuzz/scripts/0000775000175000017500000000000015060514305017275 5ustar memeupstream-fastnetmon/src/tests/fuzz/scripts/afl_pers_mod_instr.sh0000775000175000017500000000146115060514305023507 0ustar meme#!/bin/bash if [ "$#" -ne 1 ]; then echo "Usage: $0 " echo "Example: $0 sflow_plugin/sflow_collector.cpp" echo "Example: $0 netflow_plugin/netflow_collector.cpp" exit 1 fi FILE=$1 # Проверяем, что файл существует if [ ! -f "$FILE" ]; then echo "Error: File $FILE does not exist." exit 1 fi # ADd __AFL_FUZZ_INIT() in beginnig of file sed -i '1i __AFL_FUZZ_INIT();' "$FILE" # Change while(true) by while(__AFL_LOOP(10000)) sed -i 's/while\s*(\s*true\s*)/while (__AFL_LOOP(10000))/' "$FILE" sed -i 's/char \s*udp_buffer\s*\[\s*udp_buffer_size\s*\]/unsigned char * udp_buffer = __AFL_FUZZ_TESTCASE_BUF;/' "$FILE" sed -i 's/int received_bytes = recvfrom([^;]*);/int received_bytes = __AFL_FUZZ_TESTCASE_LEN;/' "$FILE" echo "Instrumentation completed for file: $FILE" upstream-fastnetmon/src/tests/fuzz/scripts/start_fuzz_conf_file.sh0000775000175000017500000000213315060514305024052 0ustar meme#!/bin/bash SESSION_NAME="config_file_fuzz" INPUT_DIR="./input" OUTPUT_DIR="/output" TARGET_PROGRAM="/src/build_fuzz_harness/fastnetmon" if [ ! -d "$INPUT_DIR" ]; then echo "Input directory '$INPUT_DIR' does not exist. Creating it..." mkdir -p "$INPUT_DIR" fi if [ ! -d "$OUTPUT_DIR" ]; then echo "Output directory '$OUTPUT_DIR' does not exist. Creating it..." mkdir -p "$OUTPUT_DIR" fi if [ ! -f "$TARGET_PROGRAM" ]; then echo "Target program '$TARGET_PROGRAM' not found." exit 1 fi echo "1" >> "$INPUT_DIR"/1 echo "a" >> "$INPUT_DIR"/2 echo core | tee /proc/sys/kernel/core_pattern tmux new-session -d -s $SESSION_NAME -n afl1 tmux send-keys -t ${SESSION_NAME}:afl1 "afl-fuzz -i $INPUT_DIR -o $OUTPUT_DIR -m none -M master -- $TARGET_PROGRAM --configuration_check --configuration_file @@" C-m tmux new-window -t $SESSION_NAME -n afl2 tmux send-keys -t ${SESSION_NAME}:afl2 "afl-fuzz -i $INPUT_DIR -o $OUTPUT_DIR -m none -S slave -- $TARGET_PROGRAM --configuration_check --configuration_file @@" C-m tmux select-window -t ${SESSION_NAME}:afl1 tmux attach-session -t $SESSION_NAME upstream-fastnetmon/src/tests/fuzz/scripts/start_fuzz_harness.sh0000775000175000017500000000404715060514305023577 0ustar meme#!/bin/bash # Check if two arguments are provided if [ $# -ne 1 ]; then echo "Usage: $0 <./bin>" echo "Example: $0 ../../../build_fuzz_harness/parse_sflow_v5_packet_fuzz" echo "Example: $0 ../../../build_fuzz_harness/process_netflow_packet_v5_fuzz" exit 1 fi TARGET_PROGRAM="$1" ASAN_OPTIONS="detect_odr_violation=0:abort_on_error=1:symbolize=0" TIME_STOP=3600 INPUT_DIR="./input" OUTPUT_DIR="/output" DIR_NAME=$(basename $1)_dir DICT="/AFLplusplus/dictionaries/pcap.dict" MINIMIZE_SCRIPT=/src/tests/fuzz/scripts/minimize_out.sh SESSION_NAME="$DIR_NAME" if [ ! -d "$DIR_NAME" ]; then echo "Work directory '$DIR_NAME' does not exist. Creating it..." mkdir -p "$DIR_NAME" fi cd $DIR_NAME TARGET_PROGRAM=../"$1" if [ ! -d "$INPUT_DIR" ]; then echo "Input directory '$INPUT_DIR' does not exist. Creating it..." mkdir -p "$INPUT_DIR" fi if [ ! -d "$OUTPUT_DIR" ]; then echo "Output directory '$OUTPUT_DIR' does not exist. Creating it..." mkdir -p "$OUTPUT_DIR" fi if [ ! -f "$TARGET_PROGRAM" ]; then echo "Target program '$TARGET_PROGRAM' not found." exit 1 fi if [ ! -x "$MINIMIZE_SCRIPT" ]; then echo "Minimization script not found or not executable." exit 1 fi echo core | tee /proc/sys/kernel/core_pattern wget https://raw.githubusercontent.com/catalyst/openstack-sflow-traffic-billing/refs/heads/master/examples/sample-sflow-packet -O input/1 echo "1" >> input/2 tmux new-session -d -s $SESSION_NAME -n master tmux send-keys -t ${SESSION_NAME}:master "ASAN_OPTIONS=$ASAN_OPTIONS AFL_EXIT_ON_TIME=$TIME_STOP afl-fuzz -i $INPUT_DIR -o $OUTPUT_DIR -x $DICT -m none -M master -- ./$TARGET_PROGRAM " C-m tmux select-window -t ${SESSION_NAME}:master tmux attach-session -t $SESSION_NAME # Wait for all afl-fuzz processes to finish while pgrep -x "afl-fuzz" > /dev/null; do echo "Waiting for afl-fuzz processes to finish..." sleep 10 done echo "All afl-fuzz processes have completed. Stopping tmux sessions." tmux kill-session -t $SESSION_NAME echo "Starting minimization." $MINIMIZE_SCRIPT $OUTPUT_DIR $TARGET_PROGRAM upstream-fastnetmon/src/tests/fuzz/scripts/minimize_out.sh0000775000175000017500000000232515060514305022346 0ustar meme#!/bin/bash export ASAN_OPTIONS=detect_odr_violation=0:abort_on_error=1:symbolize=0 # Check if two arguments are provided if [ $# -ne 2 ]; then echo "Usage: $0 " exit 1 fi # Get arguments out_dir="$1" binary="$2" index=1 # Check if the out directory exists if [ ! -d "$out_dir" ]; then echo "Error: Directory $out_dir does not exist!" exit 1 fi # Check if the binary exists and is executable if [ ! -f "$binary" ] || [ ! -x "$binary" ]; then echo "Error: Binary file $binary does not exist or is not executable!" exit 1 fi # Create output directory if it doesn't exist if [ ! -d "new_out" ]; then mkdir new_out echo "Directory new_out has been created." fi # Check if there are files in the out directory if [ -z "$(ls -A "$out_dir")" ]; then echo "No files found in $out_dir!" exit 1 fi # Iterate over files in the out directory for file in "$out_dir"/*/crashes/*; do # Check if the current item is a file if [ -f "$file" ]; then echo "Processing file: $file" casr-san -o "new_out/$index.casrep" -- "$binary" < "$file" ((index++)) else echo "Skipped (not a file): $file" fi done casr-cluster -c "new_out" out-cluster cp -r out-cluster $out_dir/upstream-fastnetmon/src/tests/parser_performance_tests.cpp0000664000175000017500000000751715060514305022425 0ustar meme#include #include #include #include #include #include #include using namespace Tins; /* gcc ../fastnetmon_packet_parser.c -o fastnetmon_packet_parser.o -c g++ parser_performance_tests.cpp fastnetmon_packet_parser.o -lpthread -ltins -std=c++11 */ /* Tins: C++ 98 We process: 3 557 647 pps We process: 3 554 012 pps Tins: C++11 We process: 3 529 692 pps We process: 3 529 249 pps PF_RING packet parser without hashing and timestamps: We process: 18 145 597 pps We process: 20 395 563 pps We process: 18 145 597 pps We process: 20 395 563 pps */ void call_fastnetmon_parser(void* ptr, int length); void call_tins_parser(void* ptr, int length); uint64_t received_packets = 0; void* speed_printer(void* ptr) { while (1) { uint64_t packets_before = received_packets; sleep(1); uint64_t packets_after = received_packets; uint64_t pps = packets_after - packets_before; printf("We process: %llu pps\n", pps); } } // We could print any payload with this function and use it for tests void print_packet_payload_in_c_form(unsigned char* data, int length) { int i = 0; printf("unsigned char payload[] = { "); for (i = 0; i < length; i++) { printf("0x%02X", (unsigned char)data[i]); if (i != length - 1) { printf(","); } } printf(" }\n"); } int main() { pthread_t thread; pthread_create(&thread, NULL, speed_printer, NULL); pthread_detach(thread); unsigned char payload1[] = { 0x90, 0xE2, 0xBA, 0x83, 0x3F, 0x25, 0x90, 0xE2, 0xBA, 0x2C, 0xCB, 0x02, 0x08, 0x00, 0x45, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x40, 0x06, 0x69, 0xDC, 0x0A, 0x84, 0xF1, 0x83, 0x0A, 0x0A, 0x0A, 0xDD, 0x04, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x0A, 0x9A, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; unsigned char payload2[] = { 0x90, 0xE2, 0xBA, 0x83, 0x3F, 0x25, 0x90, 0xE2, 0xBA, 0x2C, 0xCB, 0x02, 0x08, 0x00, 0x45, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x40, 0x06, 0x69, 0xDB, 0x0A, 0x84, 0xF1, 0x84, 0x0A, 0x0A, 0x0A, 0xDD, 0x04, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x0A, 0x9A, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; unsigned char byte_value = 0; // int counter = 512; // while (counter > 0) { while (1) { // We use overflow here! byte_value++; // payload1[26] = byte_value; // first octet payload1[29] = byte_value; // last octet call_fastnetmon_parser((void*)payload1, sizeof(payload1)); // call_tins_parser((void*)payload1, sizeof(payload1)); } } void call_tins_parser(void* ptr, int length) { __sync_fetch_and_add(&received_packets, 1); EthernetII pdu((const uint8_t*)ptr, length); const IP& ip = pdu.rfind_pdu(); // Find the IP layer if (ip.protocol() == Tins::Constants::IP::PROTO_TCP) { const TCP& tcp = pdu.rfind_pdu(); // Find the TCP layer // std::cout << ip.src_addr() << ':' << tcp.sport() << " -> " // << ip.dst_addr() << ':' << tcp.dport() << std::endl; } else if (ip.protocol() == Tins::Constants::IP::PROTO_UDP) { const UDP& udp = pdu.rfind_pdu(); // Find the UDP layer } else if (ip.protocol() == Tins::Constants::IP::PROTO_ICMP) { const ICMP& icmp = pdu.rfind_pdu(); // Find the ICMP layer } } void call_fastnetmon_parser(void* ptr, int length) { __sync_fetch_and_add(&received_packets, 1); // Call ng parser here } upstream-fastnetmon/src/tests/build_lpm_test.bash0000755000175000017500000000067414230006537020466 0ustar meme#!/usr/bin/env bash COMPILER=clang CPP_COMPILER=clang++ gcc -g -pg -O2 ../libpatricia/patricia.c -c -o patricia.o g++ -g -pg -O2 lpm_performance_tests.cpp patricia.o -olpm_performance_tests -lrt #$COMPILER -O4 ../libpatricia/patricia.c -c -o patricia.o #ar q patricia.a patricia.o #$CPP_COMPILER lpm_performance_tests.cpp -olpm_performance_tests.o -c #$CPP_COMPILER -v -O4 lpm_performance_tests.o patricia.a -olpm_performance_tests -lrt upstream-fastnetmon/src/tests/traffic_structures_performance_tests.cpp0000664000175000017500000012466415060514305025055 0ustar meme#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../fast_endianless.hpp" #include #include #include #include "../fastnetmon_types.hpp" #include "../all_logcpp_libraries.hpp" #ifdef TEST_TBB_LIBRARY #ifndef __APPLE__ #include "tbb/concurrent_unordered_map.h" #endif #endif #include #include #ifdef ABSEIL_TESTS #include "absl/container/flat_hash_map.h" #include "absl/container/node_hash_map.h" #endif // It's not enabled because it crashes: https://github.com/sparsehash/sparsehash/issues/166 //#define TEST_SPARSE_HASH #ifdef TEST_SPARSE_HASH #include #endif #include "../fast_library.hpp" log4cpp::Category& logger = log4cpp::Category::getRoot(); std::mutex data_counter_mutex; struct eqint { bool operator()(uint32_t a, uint32_t b) const { return a == b; } }; using namespace std; int number_of_ips = 10 * 1000 * 1000; int number_of_retries = 1; // #define enable_mutexes_in_test unsigned int number_of_threads = 1; key_t generate_ipc_key() { // 121 is unique project id key_t ipc_key = ftok(__FILE__, 121); if (ipc_key < 0) { std::cerr << "Failed to Generate IPC Key" << std::endl; return 0; } return ipc_key; } template void packet_collector(T& data_structure) { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexes_in_test data_counter_mutex.lock(); #endif data_structure[i].udp.in_bytes++; #ifdef enable_mutexes_in_test data_counter_mutex.unlock(); #endif } } } template void packet_collector_big_endian(T& data_structure) { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexes_in_test data_counter_mutex.lock(); #endif // Explicitly convert data to big endian to emulate our logic closely data_structure[fast_hton(i)].udp.in_bytes++; #ifdef enable_mutexes_in_test data_counter_mutex.unlock(); #endif } } } // This function only implements conversion to big endian and accumulates result template void packet_collector_big_endian_conversion_only(T& accumulator) { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexes_in_test data_counter_mutex.lock(); #endif // Explicitly convert data to big endian to emulate our logic closely accumulator += fast_hton(i); #ifdef enable_mutexes_in_test data_counter_mutex.unlock(); #endif } } } // We use it to avoid compiler to drop this value uint64_t value_accumulator = 0; // This function does full scan over hash table template void do_full_table_scan(T& accumulator) { for (auto& elem : accumulator) { #ifdef enable_mutexes_in_test data_counter_mutex.lock(); #endif value_accumulator += elem.second.udp.in_bytes; #ifdef enable_mutexes_in_test data_counter_mutex.unlock(); #endif } } // This function does full scan over vector template void do_full_table_scan_vector(T& accumulator) { for (auto& elem : accumulator) { #ifdef enable_mutexes_in_test data_counter_mutex.lock(); #endif value_accumulator += elem.udp.in_bytes; #ifdef enable_mutexes_in_test data_counter_mutex.unlock(); #endif } } // We just execute time read here void packet_collector_time_calculaitons(int) { struct timespec current_time; for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { clock_gettime(CLOCK_REALTIME, ¤t_time); } } } // We just execute time read here void packet_collector_time_calculaitons_monotonic(int) { struct timespec current_time; for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { clock_gettime(CLOCK_MONOTONIC, ¤t_time); } } } // We just execute time read here void packet_collector_time_calculaitons_monotonic_coarse(int) { struct timespec current_time; for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { clock_gettime(CLOCK_MONOTONIC_COARSE, ¤t_time); } } } // We just execute time read here void packet_collector_time_calculaitons_gettimeofday(int) { struct timeval current_time; for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { gettimeofday(¤t_time, NULL); } } } // We just execute time read here /* void packet_collector_time_calculaitons_rdtsc(int) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-but-set-variable" uint64_t current_time; for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { current_time = read_tsc_cpu_register(); } } #pragma GCC diagnostic pop } */ template int run_tests(double total_operations, std::function tested_function, T& value) { timeval start_time; gettimeofday(&start_time, NULL); // std::cout << "Run "<< number_of_threads <<" threads" << endl; boost::thread* threads[number_of_threads]; for (int i = 0; i < number_of_threads; i++) { threads[i] = new boost::thread(tested_function, boost::ref(value)); } // std::cout << "All threads started" << endl; // std::cout << "Wait for finishing" << endl; for (int i = 0; i < number_of_threads; i++) { threads[i]->join(); } // cout << "All threads finished" << endl; timeval finish_time; gettimeofday(&finish_time, NULL); // We use ' for pretty print of long numbers // http://stackoverflow.com/questions/1499156/convert-astronomically-large-numbers-into-human-readable-form-in-c-c setlocale(LC_NUMERIC, "en_US.utf-8"); /* important */ timeval interval; timeval_subtract(&interval, &finish_time, &start_time); // Build time with float part double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; // printf("We spent %f seconds\n", used_time); double ops_per_second = total_operations / used_time; double mega_ops_per_second = ops_per_second / 1000 / 1000; printf("%'.1f mega ops per second\n", mega_ops_per_second); return 0; } void init_logging() { log4cpp::PatternLayout* console_layout = new log4cpp::PatternLayout(); console_layout->setConversionPattern("%d [%p] %m%n"); log4cpp::Appender* console_appender = new log4cpp::OstreamAppender("console", &std::cout); console_appender->setLayout(console_layout); logger.setPriority(log4cpp::Priority::DEBUG); logger.addAppender(console_appender); } int main(int argc, char* argv[]) { init_logging(); double total_operations = number_of_ips * number_of_retries * number_of_threads; bool test_monotonic_coarse = false; bool test_gettimeofday = false; bool test_std_map = false; bool test_std_map_big_endian = false; bool test_tbb_concurrent_unordered_map = false; bool test_boost_unordered_map = false; bool test_boost_unordered_map_big_endian = false; bool test_boost_unordered_map_preallocated = false; bool test_boost_unordered_map_preallocated_big_endian = false; bool test_boost_unordered_map_precreated = false; bool test_boost_unordered_map_precreated_big_endian = false; bool test_boost_unordered_flat_map = false; bool test_boost_unordered_flat_map_big_endian = false; bool test_boost_unordered_flat_map_preallocated = false; bool test_boost_unordered_flat_map_preallocated_big_endian = false; bool test_boost_unordered_flat_map_precreated = false; bool test_boost_unordered_flat_map_precreated_big_endian = false; bool test_boost_container_flat_map = false; bool test_unordered_map_cpp11 = false; bool test_unordered_map_cpp11_big_endian = false; bool test_unordered_map_cpp11_preallocated = false; bool test_unordered_map_cpp11_precreated = false; bool test_vector_preallocated = false; bool test_std_map_precreated = false; bool test_clock_gettime_realtime = false; bool test_clock_gettime_monotonic = false; bool test_rdtsc_time = false; bool test_c_array_preallocated = false; bool test_c_array_huge_pages_preallocated = false; bool tests_endian_less_conversion = false; if (argc > 1) { std::string first_argument = argv[1]; if (first_argument == "test_std_map") { test_std_map = true; } else if (first_argument == "test_std_map_big_endian") { test_std_map_big_endian = true; } else if (first_argument == "tests_endian_less_conversion") { tests_endian_less_conversion = true; } else if (first_argument == "test_tbb_concurrent_unordered_map") { test_tbb_concurrent_unordered_map = true; } else if (first_argument == "test_boost_unordered_map") { test_boost_unordered_map = true; } else if (first_argument == "test_boost_unordered_map_big_endian") { test_boost_unordered_map_big_endian = true; } else if (first_argument == "test_boost_unordered_map_preallocated") { test_boost_unordered_map_preallocated = true; } else if (first_argument == "test_boost_unordered_map_preallocated_big_endian") { test_boost_unordered_map_preallocated_big_endian = true; } else if (first_argument == "test_boost_unordered_map_precreated") { test_boost_unordered_map_precreated = true; } else if (first_argument == "test_boost_unordered_map_precreated_big_endian") { test_boost_unordered_map_precreated_big_endian = true; } else if (first_argument == "test_boost_container_flat_map") { test_boost_container_flat_map = true; } else if (first_argument == "test_unordered_map_cpp11") { test_unordered_map_cpp11 = true; } else if (first_argument == "test_unordered_map_cpp11_big_endian") { test_unordered_map_cpp11_big_endian = true; } else if (first_argument == "test_unordered_map_cpp11_preallocated") { test_unordered_map_cpp11_preallocated = true; } else if (first_argument == "test_vector_preallocated") { test_vector_preallocated = true; } else if (first_argument == "test_unordered_map_cpp11_precreated") { test_unordered_map_cpp11_precreated = true; } else if (first_argument == "test_std_map_precreated") { test_std_map_precreated = true; } else if (first_argument == "test_clock_gettime_realtime") { test_clock_gettime_realtime = true; } else if (first_argument == "test_clock_gettime_monotonic") { test_clock_gettime_monotonic = true; } else if (first_argument == "test_rdtsc_time") { test_rdtsc_time = true; } else if (first_argument == "test_gettimeofday") { test_gettimeofday = true; } else if (first_argument == "test_c_array_preallocated") { test_c_array_preallocated = true; } else if (first_argument == "test_c_array_huge_pages_preallocated") { test_c_array_huge_pages_preallocated = true; } else if (first_argument == "test_monotonic_coarse") { test_monotonic_coarse = true; } } else { test_monotonic_coarse = false; test_clock_gettime_monotonic = false; test_gettimeofday = false; test_clock_gettime_realtime = false; test_std_map = true; test_std_map_big_endian = true; test_std_map_precreated = true; test_tbb_concurrent_unordered_map = true; test_boost_unordered_map = true; test_boost_unordered_map_big_endian = true; test_boost_unordered_map_preallocated = true; test_boost_unordered_map_preallocated_big_endian = true; test_boost_unordered_map_precreated = true; test_boost_unordered_map_precreated_big_endian = true; test_boost_unordered_flat_map = true; test_boost_unordered_flat_map_big_endian = true; test_boost_unordered_flat_map_preallocated = true; test_boost_unordered_flat_map_preallocated_big_endian = true; test_boost_unordered_flat_map_precreated = true; test_boost_unordered_flat_map_precreated_big_endian = true; test_boost_container_flat_map = true; test_unordered_map_cpp11 = true; test_unordered_map_cpp11_big_endian = true; test_unordered_map_cpp11_preallocated = true; test_unordered_map_cpp11_precreated = true; test_vector_preallocated = true; test_rdtsc_time = true; test_c_array_preallocated = true; test_c_array_huge_pages_preallocated = false; tests_endian_less_conversion = true; } std::cout << "Element size: " << sizeof(subnet_counter_t) << " bytes" << std::endl; std::cout << "Total structure size: " << sizeof(subnet_counter_t) * number_of_ips / 1024 / 1024 << " Mbytes" << std::endl; std::cout << std::endl << std::endl; if (test_std_map) { std::map DataCounter; std::cout << "std::map: "; run_tests(total_operations, packet_collector>, DataCounter); std::cout << "std::map big endian keys full scan: "; run_tests(DataCounter.size(), do_full_table_scan>, DataCounter); DataCounter.clear(); } std::cout << std::endl; if (test_std_map_big_endian) { std::map DataCounter; std::cout << "std::map big endian keys: "; run_tests(total_operations, packet_collector_big_endian>, DataCounter); std::cout << "std::map big endian keys full scan: "; run_tests(DataCounter.size(), do_full_table_scan>, DataCounter); DataCounter.clear(); } std::cout << std::endl; if (test_std_map_precreated) { std::map DataCounterPrecreated; // Pre-create all elements for (uint32_t i = 0; i < number_of_ips; i++) { subnet_counter_t current_map_element; DataCounterPrecreated.insert(std::make_pair(i, current_map_element)); } std::cout << "std::map pre-created: "; run_tests(total_operations, packet_collector>, DataCounterPrecreated); std::cout << "std::map pre-created full scan: "; run_tests(DataCounterPrecreated.size(), do_full_table_scan>, DataCounterPrecreated); DataCounterPrecreated.clear(); } std::cout << std::endl << std::endl; if (test_boost_unordered_map) { boost::unordered_map DataCounterBoostUnordered; std::cout << "boost::unordered_map: "; run_tests(total_operations, packet_collector>, DataCounterBoostUnordered); std::cout << "boost::unordered_map full scan: "; run_tests(DataCounterBoostUnordered.size(), do_full_table_scan>, DataCounterBoostUnordered); DataCounterBoostUnordered.clear(); } std::cout << std::endl; if (test_boost_unordered_map_big_endian) { boost::unordered_map DataCounterBoostUnordered; std::cout << "boost::unordered_map big endian keys: "; run_tests(total_operations, packet_collector_big_endian>, DataCounterBoostUnordered); std::cout << "boost::unordered_map big endian keys full scan: "; run_tests(DataCounterBoostUnordered.size(), do_full_table_scan>, DataCounterBoostUnordered); DataCounterBoostUnordered.clear(); } std::cout << std::endl; if (test_boost_unordered_map_preallocated) { boost::unordered_map DataCounterBoostUnorderedPreallocated; std::cout << "boost::unordered_map with preallocated elements: "; DataCounterBoostUnorderedPreallocated.reserve(number_of_ips); run_tests(total_operations, packet_collector>, DataCounterBoostUnorderedPreallocated); std::cout << "boost::unordered_map with preallocated elements full scan: "; run_tests(DataCounterBoostUnorderedPreallocated.size(), do_full_table_scan>, DataCounterBoostUnorderedPreallocated); DataCounterBoostUnorderedPreallocated.clear(); } std::cout << std::endl; if (test_boost_unordered_map_preallocated_big_endian) { boost::unordered_map DataCounterBoostUnorderedPreallocated; std::cout << "boost::unordered_map big endian keys with preallocated elements: "; DataCounterBoostUnorderedPreallocated.reserve(number_of_ips); run_tests(total_operations, packet_collector_big_endian>, DataCounterBoostUnorderedPreallocated); std::cout << "boost::unordered_map big endian keys with preallocated elements full scan: "; run_tests(DataCounterBoostUnorderedPreallocated.size(), do_full_table_scan>, DataCounterBoostUnorderedPreallocated); DataCounterBoostUnorderedPreallocated.clear(); } std::cout << std::endl; if (test_boost_unordered_map_precreated) { boost::unordered_map DataCounterBoostUnorderedPrecreated; std::cout << "boost::unordered_map with pre-created elements: "; for (uint32_t i = 0; i < number_of_ips; i++) { subnet_counter_t current_map_element; DataCounterBoostUnorderedPrecreated.insert(std::make_pair(i, current_map_element)); } run_tests(total_operations, packet_collector>, DataCounterBoostUnorderedPrecreated); std::cout << "boost::unordered_map with pre-created elements full scan: "; run_tests(DataCounterBoostUnorderedPrecreated.size(), do_full_table_scan>, DataCounterBoostUnorderedPrecreated); DataCounterBoostUnorderedPrecreated.clear(); } std::cout << std::endl; if (test_boost_unordered_map_precreated_big_endian) { boost::unordered_map DataCounterBoostUnorderedPrecreated; std::cout << "boost::unordered_map big endian keys with pre-created elements: "; for (uint32_t i = 0; i < number_of_ips; i++) { subnet_counter_t current_map_element; DataCounterBoostUnorderedPrecreated.insert(std::make_pair(fast_hton(i), current_map_element)); } run_tests(total_operations, packet_collector_big_endian>, DataCounterBoostUnorderedPrecreated); std::cout << "boost::unordered_map big endian with pre-created elements full scan: "; run_tests(DataCounterBoostUnorderedPrecreated.size(), do_full_table_scan>, DataCounterBoostUnorderedPrecreated); DataCounterBoostUnorderedPrecreated.clear(); } std::cout << std::endl << std::endl; std::cout << std::endl << std::endl; if (test_boost_unordered_flat_map) { boost::unordered_flat_map DataCounterBoostUnordered; std::cout << "boost::unordered_flat_map: "; run_tests(total_operations, packet_collector>, DataCounterBoostUnordered); std::cout << "boost::unordered_flat_map full scan: "; run_tests(DataCounterBoostUnordered.size(), do_full_table_scan>, DataCounterBoostUnordered); DataCounterBoostUnordered.clear(); } std::cout << std::endl; if (test_boost_unordered_flat_map_big_endian) { boost::unordered_flat_map DataCounterBoostUnordered; std::cout << "boost::unordered_flat_map big endian keys: "; run_tests(total_operations, packet_collector_big_endian>, DataCounterBoostUnordered); std::cout << "boost::unordered_flat_map big endian keys full scan: "; run_tests(DataCounterBoostUnordered.size(), do_full_table_scan>, DataCounterBoostUnordered); DataCounterBoostUnordered.clear(); } std::cout << std::endl; std::cout << std::endl; if (test_boost_unordered_flat_map_preallocated) { boost::unordered_flat_map DataCounterBoostUnorderedPreallocated; std::cout << "boost::unordered_flat_map with preallocated elements: "; DataCounterBoostUnorderedPreallocated.reserve(number_of_ips); run_tests(total_operations, packet_collector>, DataCounterBoostUnorderedPreallocated); std::cout << "boost::unordered_flat_map with preallocated elements full scan: "; run_tests(DataCounterBoostUnorderedPreallocated.size(), do_full_table_scan>, DataCounterBoostUnorderedPreallocated); DataCounterBoostUnorderedPreallocated.clear(); } std::cout << std::endl; if (test_boost_unordered_flat_map_preallocated_big_endian) { boost::unordered_flat_map DataCounterBoostUnorderedPreallocated; std::cout << "boost::unordered_flat_map big endian keys with preallocated elements: "; DataCounterBoostUnorderedPreallocated.reserve(number_of_ips); run_tests(total_operations, packet_collector_big_endian>, DataCounterBoostUnorderedPreallocated); std::cout << "boost::unordered_flat_map big endian keys with preallocated elements full scan: "; run_tests(DataCounterBoostUnorderedPreallocated.size(), do_full_table_scan>, DataCounterBoostUnorderedPreallocated); DataCounterBoostUnorderedPreallocated.clear(); } std::cout << std::endl; if (test_boost_unordered_flat_map_precreated) { boost::unordered_flat_map DataCounterBoostUnorderedPrecreated; std::cout << "boost::unordered_flat_map with pre-created elements: "; for (uint32_t i = 0; i < number_of_ips; i++) { subnet_counter_t current_map_element; DataCounterBoostUnorderedPrecreated.insert(std::make_pair(i, current_map_element)); } run_tests(total_operations, packet_collector>, DataCounterBoostUnorderedPrecreated); std::cout << "boost::unordered_flat_map with pre-created elements full scan: "; run_tests(DataCounterBoostUnorderedPrecreated.size(), do_full_table_scan>, DataCounterBoostUnorderedPrecreated); DataCounterBoostUnorderedPrecreated.clear(); } std::cout << std::endl; if (test_boost_unordered_flat_map_precreated_big_endian) { boost::unordered_flat_map DataCounterBoostUnorderedPrecreated; std::cout << "boost::unordered_flat_map big endian keys with pre-created elements: "; for (uint32_t i = 0; i < number_of_ips; i++) { subnet_counter_t current_map_element; DataCounterBoostUnorderedPrecreated.insert(std::make_pair(fast_hton(i), current_map_element)); } run_tests(total_operations, packet_collector_big_endian>, DataCounterBoostUnorderedPrecreated); std::cout << "boost::unordered_flat_map big endian with pre-created elements full scan: "; run_tests(DataCounterBoostUnorderedPrecreated.size(), do_full_table_scan>, DataCounterBoostUnorderedPrecreated); DataCounterBoostUnorderedPrecreated.clear(); } std::cout << std::endl << std::endl; if (test_boost_container_flat_map) { boost::container::flat_map DataCounterBoostFlatMap; // Boost flat_map DataCounterBoostFlatMap.reserve(number_of_ips); std::cout << "boost::container::flat_map with preallocated elements: "; run_tests(total_operations, packet_collector>, DataCounterBoostFlatMap); std::cout << "boost::container::flat_map with pre-allocated elements full scan: "; run_tests(DataCounterBoostFlatMap.size(), do_full_table_scan>, DataCounterBoostFlatMap); DataCounterBoostFlatMap.clear(); } std::cout << std::endl; if (test_unordered_map_cpp11) { std::unordered_map DataCounterUnordered; std::cout << "std::unordered_map: "; run_tests(total_operations, packet_collector>, DataCounterUnordered); std::cout << "std::unordered_map full scan: "; run_tests(DataCounterUnordered.size(), do_full_table_scan>, DataCounterUnordered); DataCounterUnordered.clear(); } std::cout << std::endl; if (test_unordered_map_cpp11_big_endian) { std::unordered_map DataCounterUnordered; std::cout << "std::unordered_map big endian keys: "; run_tests(total_operations, packet_collector_big_endian>, DataCounterUnordered); std::cout << "std::unordered_map big endian keys full scan: "; run_tests(DataCounterUnordered.size(), do_full_table_scan>, DataCounterUnordered); DataCounterUnordered.clear(); } std::cout << std::endl; if (test_unordered_map_cpp11_preallocated) { std::unordered_map DataCounterUnorderedPreallocated; // Preallocate hash buckets DataCounterUnorderedPreallocated.reserve(number_of_ips); std::cout << "std::unordered_map preallocated buckets: "; run_tests(total_operations, packet_collector>, DataCounterUnorderedPreallocated); // std::cout << "Number of buckets: " << DataCounterUnorderedPreallocated.bucket_count() << std::endl; // std::cout << "Number of IP's: " << DataCounterUnorderedPreallocated.size() << std::endl; // std::cout << "Load factor: " << DataCounterUnorderedPreallocated.load_factor() << std::endl; std::cout << "std::unordered_map preallocated buckets full scan: "; run_tests(DataCounterUnorderedPreallocated.size(), do_full_table_scan>, DataCounterUnorderedPreallocated); DataCounterUnorderedPreallocated.clear(); } std::cout << std::endl; if (test_unordered_map_cpp11_precreated) { std::unordered_map DataCounterUnorderedPrecreated; // Pre-create all elements in hash for (uint32_t i = 0; i < number_of_ips; i++) { subnet_counter_t current_map_element; DataCounterUnorderedPrecreated.insert(std::make_pair(i, current_map_element)); } std::cout << "std::unordered_map pre-created elements: "; run_tests(total_operations, packet_collector>, DataCounterUnorderedPrecreated); std::cout << "std::unordered_map pre-created elements full scan: "; run_tests(DataCounterUnorderedPrecreated.size(), do_full_table_scan>, DataCounterUnorderedPrecreated); DataCounterUnorderedPrecreated.clear(); } std::cout << std::endl << std::endl; #ifdef ABSEIL_TESTS { absl::flat_hash_map DataCounterAbseilFlatHashMap; std::cout << "abesil::flat_hash_map: "; run_tests(total_operations, packet_collector>, DataCounterAbseilFlatHashMap); std::cout << "abesil::flat_hash_map full scan: "; run_tests(DataCounterAbseilFlatHashMap.size(), do_full_table_scan>, DataCounterAbseilFlatHashMap); DataCounterAbseilFlatHashMap.clear(); } #endif std::cout << std::endl << std::endl; #ifdef ABSEIL_TESTS { absl::flat_hash_map DataCounterAbseilFlatHashMap; // Pre-create all elements in hash for (uint32_t i = 0; i < number_of_ips; i++) { subnet_counter_t current_map_element; DataCounterAbseilFlatHashMap.insert(std::make_pair(i, current_map_element)); } std::cout << "abesil::flat_hash_map pre-created elements : "; run_tests(total_operations, packet_collector>, DataCounterAbseilFlatHashMap); std::cout << "abesil::flat_hash_map pre-created elements full scan: "; run_tests(DataCounterAbseilFlatHashMap.size(), do_full_table_scan>, DataCounterAbseilFlatHashMap); DataCounterAbseilFlatHashMap.clear(); } #endif std::cout << std::endl << std::endl; #ifdef ABSEIL_TESTS { absl::node_hash_map DataCounterAbseilNodeHashMap; std::cout << "abesil::node_hash_map: "; run_tests(total_operations, packet_collector>, DataCounterAbseilNodeHashMap); std::cout << "abesil::node_hash_map full scan: "; run_tests(DataCounterAbseilNodeHashMap.size(), do_full_table_scan>, DataCounterAbseilNodeHashMap); DataCounterAbseilNodeHashMap.clear(); } #endif std::cout << std::endl << std::endl; #ifdef ABSEIL_TESTS { absl::node_hash_map DataCounterAbseilNodeHashMap; // Pre-create all elements in hash for (uint32_t i = 0; i < number_of_ips; i++) { subnet_counter_t current_map_element; DataCounterAbseilNodeHashMap.insert(std::make_pair(i, current_map_element)); } std::cout << "abesil::node_hash_map pre-created elements: "; run_tests(total_operations, packet_collector>, DataCounterAbseilNodeHashMap); std::cout << "abesil::node_hash_map pre-created elements full scan: "; run_tests(DataCounterAbseilNodeHashMap.size(), do_full_table_scan>, DataCounterAbseilNodeHashMap); DataCounterAbseilNodeHashMap.clear(); } #endif std::cout << std::endl << std::endl; #ifdef TEST_SPARSE_HASH google::dense_hash_map, eqint, google::libc_allocator_with_realloc>> DataCounterGoogleDensehashMap; std::cout << "google:dense_hashmap without preallocation: "; DataCounterGoogleDensehashMap.set_empty_key(UINT32_MAX); // We will got assert without it! run_tests(total_operations, packet_collector, eqint, google::libc_allocator_with_realloc>>>, DataCounterGoogleDensehashMap); DataCounterGoogleDensehashMap.clear(); #endif #ifdef TEST_SPARSE_HASH google::dense_hash_map, eqint, google::libc_allocator_with_realloc>> DataCounterGoogleDensehashMapPreallocated; std::cout << "google:dense_hashmap preallocated buckets: "; // We use UINT32_MAX as "empty" here, not a good idea but OK for tests DataCounterGoogleDensehashMapPreallocated.set_empty_key(UINT32_MAX); // We will got assert without it! DataCounterGoogleDensehashMapPreallocated.resize(number_of_ips); run_tests(total_operations, packet_collector, eqint, google::libc_allocator_with_realloc>>>, DataCounterGoogleDensehashMapPreallocated); DataCounterGoogleDensehashMapPreallocated.clear(); #endif if (test_tbb_concurrent_unordered_map) { #ifdef TEST_TBB_LIBRARY #ifndef __APPLE_ tbb::concurrent_unordered_map DataCounterUnorderedConcurrent; std::cout << "tbb::concurrent_unordered_map: "; run_tests(total_operations, packet_collector>, DataCounterUnorderedConcurrent); DataCounterUnorderedConcurrent.clear(); #endif #endif } std::cout << std::endl; if (test_vector_preallocated) { std::vector DataCounterVector(number_of_ips); std::cout << "std::vector preallocated: "; run_tests(total_operations, packet_collector>, DataCounterVector); std::cout << "std::vector full scan: "; run_tests(DataCounterVector.size(), do_full_table_scan_vector>, DataCounterVector); DataCounterVector.clear(); } std::cout << std::endl; if (test_c_array_preallocated) { subnet_counter_t* data_counter_c_array_ptr = nullptr; data_counter_c_array_ptr = new subnet_counter_t[number_of_ips]; std::cout << "C array preallocated: "; run_tests(total_operations, packet_collector, data_counter_c_array_ptr); delete[] data_counter_c_array_ptr; data_counter_c_array_ptr = NULL; } if (test_c_array_huge_pages_preallocated) { // Here you could find awesome example for this option: // http://lxr.free-electrons.com/source/tools/testing/selftests/vm/hugepage-shm.c uint32_t required_number_of_bytes = sizeof(subnet_counter_t) * number_of_ips; std::map meminfo_map; bool parse_meminfo = parse_meminfo_into_map(meminfo_map); if (!parse_meminfo) { std::cerr << "Could not parse meminfo" << std::endl; exit(-1); } uint64_t required_number_of_hugetlb_pages = ceil(required_number_of_bytes / meminfo_map["Hugepagesize"]); // std::cout << "We need least: " << required_number_of_hugetlb_pages << " of huge TLB pages" << std::endl; if (meminfo_map["HugePages_Free"] < required_number_of_hugetlb_pages) { // std::cerr << "We need " << required_number_of_hugetlb_pages << " hugetlb pages. But we have only: " << // meminfo_map["HugePages_Free"] << std::endl; std::cerr << "Let's try to allocated required number of pages" << std::endl; std::string allocate_required_number_of_huge_pages = "echo " + std::to_string(required_number_of_hugetlb_pages) + "> /proc/sys/vm/nr_hugepages"; exec_no_error_check(allocate_required_number_of_huge_pages); } // If huge tlb pages allocation failed uint64_t we_have_huge_tlb_memory = meminfo_map["Hugepagesize"] * meminfo_map["HugePages_Free"]; if (we_have_huge_tlb_memory < required_number_of_bytes) { std::cerr << "We need least " << required_number_of_bytes << " bytes but we have free only: " << we_have_huge_tlb_memory << std::endl; std::cerr << "Total number of huge pages is: " << meminfo_map["HugePages_Total"] << std::endl; exit(-1); } // Also we teed to tune kernel options about maximum shm memory segment size and overall shm memory size for // whole system becuase by default they are pretty low // std::cerr << "We need " << required_number_of_bytes << " bytes"<< std::endl; // TODO: please use print to handle for correct error handling because echo ignores all write errors std::string increase_shm_all_command = "echo " + std::to_string(required_number_of_bytes) + " > /proc/sys/kernel/shmall"; std::string increase_shm_max_command = "echo " + std::to_string(required_number_of_bytes) + " > /proc/sys/kernel/shmmax"; exec_no_error_check(increase_shm_all_command); exec_no_error_check(increase_shm_max_command); // Let's check new values right now! int shm_all_value = 0; bool read_shm_all_result = read_integer_from_file("/proc/sys/kernel/shmall", shm_all_value); if (!read_shm_all_result) { std::cerr << "Could not read shm all from /proc" << std::endl; exit(1); } int shm_max_value = 0; bool read_shm_max_result = read_integer_from_file("/proc/sys/kernel/shmmax", shm_max_value); if (!read_shm_max_result) { std::cerr << "Could not read shm max from /proc" << std::endl; exit(1); } if (shm_all_value < required_number_of_bytes) { std::cerr << "Could not set shm all to required value" << std::endl; exit(1); } if (shm_max_value < required_number_of_bytes) { std::cerr << "Could not set shm max to required value" << std::endl; exit(1); } // In 3.8 or more recent we could use SHM_HUGE_2MB and SHM_HUGE_1GB flag options! But unfortunately on // Ubuntu 14.04 we haven't this auto ipc_key = generate_ipc_key(); if (ipc_key == 0) { std::cerr << "Failed to generate ipc key" << std::endl; exit(1); } // std::cout << "IPC key is: " << ipc_key << std::endl; int shmid = shmget(ipc_key, required_number_of_bytes, SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W); if (shmid == -1) { std::cerr << "shmget failed with code: " << errno << " error as text: " << strerror(errno) << std::endl; exit(-1); } // std::cerr << "Correctly allocated hugetlb" << std::endl; subnet_counter_t* data_counter_c_array_ptr_huge_tlb = nullptr; data_counter_c_array_ptr_huge_tlb = (subnet_counter_t*)shmat(shmid, 0, 0); if (data_counter_c_array_ptr_huge_tlb == (subnet_counter_t*)-1) { std::cerr << "Could not get address of TLB shm memory" << std::endl; exit(2); } // We need to fill memory with allocated structures for (uint32_t i = 0; i < number_of_ips; i++) { subnet_counter_t new_blak_map_element; memcpy(&data_counter_c_array_ptr_huge_tlb[i], &new_blak_map_element, sizeof(new_blak_map_element)); } std::cout << "C array preallocated with huge tlb: "; run_tests(total_operations, packet_collector, data_counter_c_array_ptr_huge_tlb); // std::cerr << "Deallocate tlb" << std::endl; // That's very important! Without this option allocated huge tlb memory will be accounted in "HugePages_Rsvd" of // /proc/meminfo. // I'm not sure it's dangerous but will be fine to close all handles properly shmctl(shmid, IPC_RMID, NULL); } if (tests_endian_less_conversion) { std::cout << "endian-less: "; // To trick compiler not to optimise it uint32_t accumulator = 0; run_tests(total_operations, packet_collector_big_endian_conversion_only, accumulator); } // Fake value to make template logic happy int fake_int = 0; if (test_clock_gettime_monotonic) { std::cout << "clock_gettime CLOCK_MONOTONIC: "; run_tests(total_operations, packet_collector_time_calculaitons_monotonic, fake_int); } // According to https://fossies.org/dox/glibc-2.23/sysdeps_2unix_2clock__gettime_8c_source.html clock_gettime with // CLOCK_REALTIME is a just shortcut for gettimeofday if (test_clock_gettime_realtime) { std::cout << "clock_gettime CLOCK_REALTIME: "; run_tests(total_operations, packet_collector_time_calculaitons, fake_int); } /* if (test_rdtsc_time) { std::cout << "rdtsc assembler instruction: "; run_tests(total_operations, packet_collector_time_calculaitons_rdtsc, fake_int); } */ if (test_gettimeofday) { std::cout << "gettimeofday: "; run_tests(total_operations, packet_collector_time_calculaitons_gettimeofday, fake_int); } if (test_monotonic_coarse) { std::cout << "clock_gettime CLOCK_MONOTONIC_COARSE: "; run_tests(total_operations, packet_collector_time_calculaitons_monotonic_coarse, fake_int); } } upstream-fastnetmon/src/tests/pcap_writer.cpp0000644000175000017500000000333114232165256017640 0ustar meme#include #include #include #include #include #include #include #include #include "../fastnetmon_pcap_format.h" #include "../packet_storage.h" int main() { packet_storage_t packet_storage; // We specify in in packets if (!packet_storage.allocate_buffer(500)) { printf("Can't allocate buffer"); return -1; } unsigned char payload1[] = { 0x90, 0xE2, 0xBA, 0x83, 0x3F, 0x25, 0x90, 0xE2, 0xBA, 0x2C, 0xCB, 0x02, 0x08, 0x00, 0x45, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x40, 0x06, 0x69, 0xDC, 0x0A, 0x84, 0xF1, 0x83, 0x0A, 0x0A, 0x0A, 0xDD, 0x04, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x0A, 0x9A, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; if (!packet_storage.write_packet(payload1, sizeof(payload1))) { printf("Can't write packet to the storage\n"); return -1; } // Dump buffer to memory std::string pcap_file_path = "/tmp/fastnetmon_example.pcap"; int filedesc = open(pcap_file_path.c_str(), O_WRONLY | O_CREAT); if (filedesc <= 0) { printf("Can't open dump file for writing"); return -1; } std::cout << "Used size: " << packet_storage.get_used_memory() << std::endl; ssize_t wrote_bytes = write(filedesc, (void*)packet_storage.get_buffer_pointer(), packet_storage.get_used_memory()); if (wrote_bytes != packet_storage.get_used_memory()) { printf("Can't write data to the file\n"); return -1; } close(filedesc); return (0); } upstream-fastnetmon/src/tests/ip_lookup.cpp0000644000175000017500000002116514232165256017327 0ustar meme#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; vector exec(string cmd) { vector output_list; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) return output_list; char buffer[256]; std::string result = ""; while (!feof(pipe)) { if (fgets(buffer, 256, pipe) != NULL) { size_t newbuflen = strlen(buffer); // remove newline at the end if (buffer[newbuflen - 1] == '\n') { buffer[newbuflen - 1] = '\0'; } output_list.push_back(buffer); } } pclose(pipe); return output_list; } typedef pair subnet; bool belongs_to_networks(vector& networks_list, uint32_t ip) { for (vector::iterator ii = networks_list.begin(); ii != networks_list.end(); ++ii) { if ((ip & (*ii).second) == ((*ii).first & (*ii).second)) { return true; } } return false; } uint32_t convert_ip_as_string_to_uint(string ip) { struct in_addr ip_addr; inet_aton(ip.c_str(), &ip_addr); // in network byte order return ip_addr.s_addr; } uint32_t convert_cidr_to_binary_netmask(int cidr) { uint32_t binary_netmask = 0xFFFFFFFF; binary_netmask = binary_netmask << (32 - cidr); // htonl from host byte order to network // ntohl from network byte order to host // I suppose we need network byte order here return htonl(binary_netmask); } int get_bit(uint32_t number, uint32_t ip) { return 1; } typedef struct leaf { bool bit; struct leaf *right, *left; } tree_leaf; #include void insert_prefix_bitwise_tree(tree_leaf* root, string subnet, int cidr_mask) { uint32_t netmask_as_int = convert_ip_as_string_to_uint(subnet); // std::cout<(netmask_as_int)<= 32 - cidr_mask; i--) { uint32_t result_bit = netmask_as_int & (1 << i); bool bit = result_bit == 0 ? false : true; // cout<<"Insert: "<right != NULL) { // Elemelnt already there, just switch pointer temp_root = temp_root->right; } else { // No element here, we should create it tree_leaf* new_leaf = new tree_leaf; new_leaf->right = new_leaf->left = NULL; new_leaf->bit = bit; temp_root->right = new_leaf; temp_root = new_leaf; } } else { // check left subtree if (temp_root->left != NULL) { // Elemelnt already there, just switch pointer temp_root = temp_root->left; } else { // No element here, we should create it tree_leaf* new_leaf = new tree_leaf; new_leaf->right = new_leaf->left = NULL; new_leaf->bit = bit; temp_root->left = new_leaf; temp_root = new_leaf; } } } // #include // std::cout<(netmask_as_int)<left == NULL && temp_root->right == NULL)) { return false; } // convert to host byte order ip = ntohl(ip); int bits_matched = 0; for (int i = 31; i >= 0; i--) { // cout<<"bit"<left == NULL && temp_root->right == NULL)) { if (bits_matched > 0) { // if we havent child elemets (leaf is terinal) and we have match for single bit at // lease thus, we found mask! std::cout<<"Bits matched: "<right != NULL) { temp_root = temp_root->right; bits_matched++; } else { if (temp_root->left != NULL) { return false; } else { // already checked above } } } else { if (temp_root->left != NULL) { temp_root = temp_root->left; bits_matched++; } else { if (temp_root->right != NULL) { return false; } else { // already checked above } } } } // We will repeat same checks as in begin of function. But we need it because // we could pass cycle and do not hit any terminals - both childs become zeroes if ((temp_root->left == NULL && temp_root->right == NULL)) { if (bits_matched > 0) { // if we havent child elemets (leaf is terinal) and we have match for single bit at // lease thus, we found mask! std::cout<<"Bits matched: "<left = root->right = NULL; // uint32_t ip_127 = convert_ip_as_string_to_uint("127.0.0.3"); // uint32_t ip_159 = convert_ip_as_string_to_uint("159.253.17.1"); // uint32_t ip_8 = convert_ip_as_string_to_uint("255.8.8.8"); // insert_prefix_bitwise_tree(root, "159.253.17.0", 24); // insert_prefix_bitwise_tree(root, "159.253.16.0", 24); // insert_prefix_bitwise_tree(root, "127.0.0.1", 24); // insert_prefix_bitwise_tree(root, "255.8.8.8", 32); // std::cout< networks_list_as_string; vector our_networks; vector network_list_from_config = exec("cat /etc/networks_list"); networks_list_as_string.insert(networks_list_as_string.end(), network_list_from_config.begin(), network_list_from_config.end()); for (vector::iterator ii = networks_list_as_string.begin(); ii != networks_list_as_string.end(); ++ii) { vector subnet_as_string; split(subnet_as_string, *ii, boost::is_any_of("/"), boost::token_compress_on); int cidr = atoi(subnet_as_string[1].c_str()); uint32_t subnet_as_int = convert_ip_as_string_to_uint(subnet_as_string[0]); uint32_t netmask_as_int = convert_cidr_to_binary_netmask(cidr); insert_prefix_bitwise_tree(root, subnet_as_string[0], cidr); subnet current_subnet = std::make_pair(subnet_as_int, netmask_as_int); our_networks.push_back(current_subnet); } uint32_t my_ip = convert_ip_as_string_to_uint("192.0.0.192"); // my_ip = ntohl(my_ip); // std::bitset<32> x(my_ip); // std::cout< #include #include #include #include #include #include "../fastnetmon_types.h" // It's very raw API implementation for connection tracking code. Due to HUGE amount of collisions // it's very slow: ~1Mpps For performance it's very close to std::map but much times more buggy :) // https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp // 64-bit hash for 64-bit platforms #define BIG_CONSTANT(x) (x##LLU) uint64_t MurmurHash64A(const void* key, int len, uint64_t seed) { const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995); const int r = 47; uint64_t h = seed ^ (len * m); const uint64_t* data = (const uint64_t*)key; const uint64_t* end = data + (len / 8); while (data != end) { uint64_t k = *data++; k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } const unsigned char* data2 = (const unsigned char*)data; switch (len & 7) { case 7: h ^= uint64_t(data2[6]) << 48; case 6: h ^= uint64_t(data2[5]) << 40; case 5: h ^= uint64_t(data2[4]) << 32; case 4: h ^= uint64_t(data2[3]) << 24; case 3: h ^= uint64_t(data2[2]) << 16; case 2: h ^= uint64_t(data2[1]) << 8; case 1: h ^= uint64_t(data2[0]); h *= m; }; h ^= h >> r; h *= m; h ^= h >> r; return h; } class conntrack_hash_struct_for_simple_packet_t { public: uint32_t src_ip; uint32_t dst_ip; uint16_t source_port; uint16_t destination_port; unsigned int protocol; bool operator==(const conntrack_hash_struct_for_simple_packet_t& rhs) { // TODO: not so smart, we should fix this! return memcmp(this, &rhs, sizeof(conntrack_hash_struct_for_simple_packet_t)) == 0; } }; // Extract only important for us fields from main simple_packet structure bool convert_simple_packet_toconntrack_hash_struct(simple_packet_t& packet, conntrack_hash_struct_for_simple_packet_t& conntrack_struct) { conntrack_struct.src_ip = packet.src_ip; conntrack_struct.dst_ip = packet.dst_ip; conntrack_struct.protocol = packet.protocol; conntrack_struct.source_port = packet.source_port; conntrack_struct.destination_port = packet.destination_port; } // Class prototype for connection tracking typedef std::vector vector_of_connetrack_structs_t; class connection_tracking_fast_storage_t { public: connection_tracking_fast_storage_t(unsigned int structure_size) { murmur_seed = 13; max_vector_size = 0; number_of_buckets = structure_size; buckets_storage.reserve(structure_size); } uint64_t get_bucket_number(conntrack_hash_struct_for_simple_packet_t& element) { uint64_t conntrack_hash = MurmurHash64A(&element, sizeof(conntrack_hash_struct_for_simple_packet_t), murmur_seed); return conntrack_hash % number_of_buckets; } bool lookup(conntrack_hash_struct_for_simple_packet_t* element) { uint64_t bucket_number = get_bucket_number(*element); vector_of_connetrack_structs_t* vector_pointer = &buckets_storage[bucket_number]; unsigned int vector_size = vector_pointer->size(); if (vector_size > max_vector_size) { max_vector_size = vector_size; if (max_vector_size > 100) { printf("We got %u collisions for key %llu\n", max_vector_size, bucket_number); } } if (vector_size == 0) { return false; } vector_of_connetrack_structs_t::iterator itr = std::find(vector_pointer->begin(), vector_pointer->end(), *element); if (itr == vector_pointer->end()) { return false; } return true; } bool insert(conntrack_hash_struct_for_simple_packet_t element) { uint64_t bucket_number = get_bucket_number(element); buckets_storage[bucket_number].push_back(element); } public: unsigned int number_of_buckets; std::vector buckets_storage; unsigned int murmur_seed; // conntrack_hash_struct_for_simple_packet_t conntrack_structure; unsigned int max_vector_size; }; connection_tracking_fast_storage_t my_conntrack_storage(32000); int main() { // fake data char data[1500]; simple_packet current_packet; // parse_raw_packet_to_simple_packet((u_char*)data, length, current_packet); conntrack_hash_struct_for_simple_packet_t conntrack_structure; convert_simple_packet_toconntrack_hash_struct(current_packet, conntrack_structure); if (my_conntrack_storage.lookup(&conntrack_structure)) { // printf("Already exists\n"); // found it } else { // printf("New\n"); my_conntrack_storage.insert(conntrack_structure); } } upstream-fastnetmon/src/tests/speed_counters_performance_test.cpp0000664000175000017500000003702215060514305023762 0ustar meme#include #include #include #include #include #include "../abstract_subnet_counters.hpp" #include "../fast_library.hpp" #include "../fastnetmon_types.hpp" #include "../fast_endianless.hpp" #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" log4cpp::Category& logger = log4cpp::Category::getRoot(); abstract_subnet_counters_t ipv4_host_counters; time_t current_inaccurate_time = 0; // Try to copy source map into vector template void try_to_copy_to_vector(T& counter_map) { std::cout << "Evaluate time required to make full copy of structure into std::vector" << std::endl; std::vector> counter_map_copy; counter_map_copy.reserve(counter_map.size()); timeval start_time_val; gettimeofday(&start_time_val, NULL); ssize_t current_index = 0; // Try making copy of traffic counters and track time for (auto& itr : counter_map) { counter_map_copy[current_index++] = itr; } timeval finish_time_val; gettimeofday(&finish_time_val, NULL); timeval interval; timeval_subtract(&interval, &finish_time_val, &start_time_val); double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; std::cout << "Copied traffic map in " << used_time << " seconds" << std::endl << std::endl; counter_map_copy.clear(); } // Try to copy source map into vector template void try_to_copy_to_vector_initilised_elements(T& counter_map) { std::cout << "Evaluate time required to make full copy of structure into std::vector with all pre-initialised elements" << std::endl; // 3) Constructs the container with count copies of elements with value value. std::vector> counter_map_copy(counter_map.size(), std::make_pair(0, subnet_counter_t{})); timeval start_time_val; gettimeofday(&start_time_val, NULL); ssize_t current_index = 0; // Try making copy of traffic counters and track time for (auto& itr : counter_map) { counter_map_copy[current_index++] = itr; } timeval finish_time_val; gettimeofday(&finish_time_val, NULL); timeval interval; timeval_subtract(&interval, &finish_time_val, &start_time_val); double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; std::cout << "Copied traffic map in " << used_time << " seconds" << std::endl << std::endl; counter_map_copy.clear(); } // Try to copy source map into array // TODO: this test cannot free up memory, you must run it as last template void try_to_copy_to_array(T& counter_map) { std::cout << "Evaluate time required to make full copy of structure into std::array" << std::endl; // I've manually set size to bit our 14.6m element array std::array, 20000000> counter_map_copy; timeval start_time_val; gettimeofday(&start_time_val, NULL); ssize_t current_index = 0; for (auto& itr : counter_map) { counter_map_copy[current_index++] = itr; } timeval finish_time_val; gettimeofday(&finish_time_val, NULL); timeval interval; timeval_subtract(&interval, &finish_time_val, &start_time_val); double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; std::cout << "Copied traffic map in " << used_time << " seconds" << std::endl << std::endl; } // Try to copy source map into std::unordered_map template void try_to_copy_to_std_unordered_map(T& counter_map, bool reserve) { std::cout << "Evaluate time required to make full copy of structure into std::unordered_map"; if (reserve) { std::cout << " with memory reserve in map"; } std::cout << std::endl; std::unordered_map counter_map_copy; if (reserve) { counter_map_copy.reserve(counter_map.size()); } timeval start_time_val; gettimeofday(&start_time_val, NULL); // Try making copy of traffic counters and track time for (const auto& itr : counter_map) { counter_map_copy[itr.first] = itr.second; } timeval finish_time_val; gettimeofday(&finish_time_val, NULL); timeval interval; timeval_subtract(&interval, &finish_time_val, &start_time_val); double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; std::cout << "Copied traffic map in " << used_time << " seconds" << std::endl << std::endl; counter_map_copy.clear(); } // Try to copy source map into std::unordered_map template void try_to_copy_to_std_unordered_map_pre_created_keys(T& counter_map) { std::cout << "Evaluate time required to make full copy of structure into std::unordered_map with pre-created keys " "as in source structure"; std::cout << std::endl; std::unordered_map counter_map_copy; // Create keys as in original structure for (const auto& itr : counter_map) { counter_map_copy[itr.first] = subnet_counter_t{}; } timeval start_time_val; gettimeofday(&start_time_val, NULL); // Try making copy of traffic counters and track time for (const auto& itr : counter_map) { counter_map_copy[itr.first] = itr.second; } timeval finish_time_val; gettimeofday(&finish_time_val, NULL); timeval interval; timeval_subtract(&interval, &finish_time_val, &start_time_val); double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; std::cout << "Copied traffic map in " << used_time << " seconds" << std::endl << std::endl; counter_map_copy.clear(); } // Copies memory from std::vector to another std::vector using memory copy template void try_to_copy_vector_to_vector_memory_copy(T& counter_map_copy, const T& counter_map) { std::cout << "Evaluate time required to make full copy of std::vector into another std::vector using memory region copy" << std::endl; // We need to allocate as much memory as source std::vector to have enough memory to do copy counter_map_copy.reserve(counter_map.size()); timeval start_time_val; gettimeofday(&start_time_val, NULL); std::size_t memory_region_size = counter_map.size() * sizeof(std::pair); // Do memory copy memcpy((void*)counter_map_copy.data(), (void*)counter_map.data(), memory_region_size); timeval finish_time_val; gettimeofday(&finish_time_val, NULL); timeval interval; timeval_subtract(&interval, &finish_time_val, &start_time_val); double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; std::cout << "Copied " << memory_region_size / 1024 / 1024 << " Mbytes of memory from vector to vector in " << used_time << " seconds" << std::endl << std::endl; } template void do_parallel_memory_copy(const T& counter_map) { // Create std::vectors from map std::array>, 4> source_vectors; for (auto& current_vector : source_vectors) { current_vector.reserve(counter_map.size()); for (auto& itr : counter_map) { current_vector.push_back(itr); } } // Target vector to receive copy std::array>, 4> target_vectors; std::thread t1([&]() { try_to_copy_vector_to_vector_memory_copy(target_vectors[0], source_vectors[0]); }); std::thread t2([&]() { try_to_copy_vector_to_vector_memory_copy(target_vectors[1], source_vectors[1]); }); std::thread t3([&]() { try_to_copy_vector_to_vector_memory_copy(target_vectors[2], source_vectors[2]); }); std::thread t4([&]() { try_to_copy_vector_to_vector_memory_copy(target_vectors[3], source_vectors[3]); }); t1.join(); t2.join(); t3.join(); t4.join(); for (auto& current_vector : source_vectors) { current_vector.clear(); } for (auto& current_vector : target_vectors) { current_vector.clear(); } } int main() { std::cout << "Load structure from disk dump" << std::endl; time_t start_time = 0; time(&start_time); try { std::ifstream deserialize_stream("/home/odintsov/speed_counters_local_ipv4_hosts.dat"); boost::archive::binary_iarchive input_archive(deserialize_stream); input_archive >> BOOST_SERIALIZATION_NVP(ipv4_host_counters); std::cout << "Loaded traffic counters from disk" << std::endl; } catch (boost::archive::archive_exception& e) { std::cout << "Internal error with loading traffic counters from disk: " << e.what() << std::endl; return 1; } catch (...) { std::cout << "Internal error with loading traffic counters from disk" << std::endl; ; return 1; } time_t finish_time = 0; time(&finish_time); std::cout << "Loaded traffic from disk in " << finish_time - start_time << " seconds" << std::endl; std::cout << "Map size " << ipv4_host_counters.counter_map.size() << std::endl; std::cout << "Map bucket count " << ipv4_host_counters.counter_map.bucket_count() << std::endl; std::cout << "Memory use by single counter map: " << ipv4_host_counters.counter_map.size() * (sizeof(subnet_counter_t) + sizeof(uint32_t)) / 1024 / 1024 << " Mb" << std::endl; /* try_to_copy_to_vector(ipv4_host_counters.counter_map); try_to_copy_to_vector_initilised_elements(ipv4_host_counters.counter_map); try_to_copy_to_std_unordered_map(ipv4_host_counters.counter_map, false); try_to_copy_to_std_unordered_map(ipv4_host_counters.counter_map, true); try_to_copy_to_std_unordered_map_pre_created_keys(ipv4_host_counters.counter_map); // Well, key findings that it does not scale as we expected ;( do_parallel_memory_copy(ipv4_host_counters.counter_map); // This test cannot free up memory, we should run it as last try_to_copy_to_array(ipv4_host_counters.counter_map); */ double speed_calc_period = 1; double average_calculation_time = 60; std::cout << "Loaded" << std::endl; std::cout << "Sharding data" << std::endl; const int sharding_value = 32; std::array, sharding_value> speed_counters; for (const auto& itr : ipv4_host_counters.counter_map) { // NB!!!! TODO: without ntoh conversion it will be 2 all the time and sharding will not work!!! int reminder = fast_ntoh(itr.first) % sharding_value; // std::cout << "value" << itr.first << "reminder: " << reminder << std::endl; speed_counters[reminder].counter_map[itr.first] = itr.second; } for (const auto& current_speed_counter : speed_counters) { std::cout << "Speed counter size: " << current_speed_counter.counter_map.size() << std::endl; } /* Lab server has: AMD Ryzen 5 3600 6-Core Processor PC: AMD Ryzen 7 5800X 8-Core Processor Lab server, 1 thread (old): Calculated speed in 3.70434 seconds Calculated speed in 3.73092 seconds Calculated speed in 3.72158 seconds Lab server, 2 threads: Calculated speed in 2.93566 seconds Calculated speed in 2.94285 seconds Calculated speed in 2.92184 seconds Lab server, 3 threads: Calculated speed in 2.17883 seconds Calculated speed in 2.13465 seconds Calculated speed in 2.09182 seconds Lab server, 4 threads: Calculated speed in 1.76031 seconds Calculated speed in 1.82056 seconds Calculated speed in 1.79133 seconds Lab server, 5 threads: Calculated speed in 1.47045 seconds Calculated speed in 1.47526 seconds Calculated speed in 1.41408 seconds Lab server, 6 threads (2.7 times better than single threaded!): Calculated speed in 1.35194 seconds Calculated speed in 1.37153 seconds Calculated speed in 1.34529 seconds PC, 1 thread (old): Calculated speed in 2.80088 seconds Calculated speed in 2.80217 seconds Calculated speed in 2.80967 seconds PC, 4 threads: Calculated speed in 1.37763 seconds Calculated speed in 1.38572 seconds Calculated speed in 1.37288 seconds PC, 6 threads: Calculated speed in 1.12384 seconds Calculated speed in 1.11926 seconds Calculated speed in 1.1167 seconds PC, 7 threads: Calculated speed in 1.02398 seconds Calculated speed in 1.0216 seconds Calculated speed in 1.01837 seconds PC, 8 threads (2.7 times better than single threaded!): Calculated speed in 1.03207 seconds Calculated speed in 1.01931 seconds Calculated speed in 1.0154 seconds GCE, c2-standard-16 (16 cores), Intel Cascade Lake, 1 thread (old): Calculated speed in 4.60883 seconds Calculated speed in 4.61683 seconds Calculated speed in 4.60568 seconds GCE, c2-standard-16 (16 cores), Intel Cascade Lake, 16 threads, (7.6 times better then single thread!): Calculated speed in 0.664504 seconds Calculated speed in 0.661836 seconds Calculated speed in 0.71273 seconds GCE, e2-highcpu-32 (32 cores), Intel Broadwell, 1 thread (old): Calculated speed in 5.68188 seconds Calculated speed in 5.70301 seconds Calculated speed in 5.69111 seconds GCE, e2-highcpu-32 (32 cores), Intel Broadwell, 16 threads (9.9 times better then single thread!): Calculated speed in 0.562593 seconds Calculated speed in 0.565208 seconds Calculated speed in 0.559615 seconds GCE, e2-highcpu-32 (32 cores), Intel Broadwell, 32 threads (13 times better then single thread!): Calculated speed in 0.425343 seconds Calculated speed in 0.425999 seconds Calculated speed in 0.434782 seconds */ while (true) { timeval start_time_val; gettimeofday(&start_time_val, NULL); std::vector threads; for (auto& current_speed_counter : speed_counters) { threads.push_back(std::thread([&]() { current_speed_counter.recalculate_speed(speed_calc_period, average_calculation_time, nullptr); })); } // Wait threads to finish for (auto& current_thread : threads) { current_thread.join(); } timeval finish_time_val; gettimeofday(&finish_time_val, NULL); timeval interval; timeval_subtract(&interval, &finish_time_val, &start_time_val); double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; std::cout << "Calculated speed in " << used_time << " seconds" << std::endl << std::endl; } /* while (true) { timeval start_time_val; gettimeofday(&start_time_val, NULL); ipv4_host_counters.recalculate_speed(speed_calc_period, average_calculation_time, nullptr); timeval finish_time_val; gettimeofday(&finish_time_val, NULL); timeval interval; timeval_subtract(&interval, &finish_time_val, &start_time_val); double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; std::cout << "Calculated speed in " << used_time << " seconds" << std::endl << std::endl; } */ return 0; } upstream-fastnetmon/src/tests/lpm_performance_tests.cpp0000664000175000017500000001475415060514305021722 0ustar meme#include #include #include #include #include #include #include #include #include #include #include #include #include #include "../libpatricia/patricia.hpp" using namespace std; // main data structure for storing traffic and speed data for all our IPs class map_element { public: map_element() : in_bytes(0), out_bytes(0), in_packets(0), out_packets(0), tcp_in_packets(0), tcp_out_packets(0), tcp_in_bytes(0), tcp_out_bytes(0), udp_in_packets(0), udp_out_packets(0), udp_in_bytes(0), udp_out_bytes(0), in_flows(0), out_flows(0), icmp_in_packets(0), icmp_out_packets(0), icmp_in_bytes(0), icmp_out_bytes(0) { } unsigned int in_bytes; unsigned int out_bytes; unsigned int in_packets; unsigned int out_packets; // Additional data for correct attack protocol detection unsigned int tcp_in_packets; unsigned int tcp_out_packets; unsigned int tcp_in_bytes; unsigned int tcp_out_bytes; unsigned int udp_in_packets; unsigned int udp_out_packets; unsigned int udp_in_bytes; unsigned int udp_out_bytes; unsigned int icmp_in_packets; unsigned int icmp_out_packets; unsigned int icmp_in_bytes; unsigned int icmp_out_bytes; unsigned int in_flows; unsigned int out_flows; }; typedef vector vector_of_counters; typedef std::map map_of_vector_counters; typedef std::pair pair_of_subnets_with_key; typedef vector vector_of_vector_counters; map_of_vector_counters SubnetVectorMap; vector_of_vector_counters SubnetVectorVector; #include void subnet_vectors_allocator(prefix_t* prefix, void* data) { uint32_t subnet_as_integer = prefix->add.sin.s_addr; u_short bitlen = prefix->bitlen; int network_size_in_ips = pow(2, 32 - bitlen); network_size_in_ips = 1; SubnetVectorMap[subnet_as_integer] = new vector_of_counters(network_size_in_ips); pair_of_subnets_with_key my_pair; my_pair.first = subnet_as_integer; my_pair.second = new vector_of_counters(network_size_in_ips); SubnetVectorVector.push_back(my_pair); } void suxx_func(unsigned long suxx) { } uint32_t convert_ip_as_string_to_uint(string ip) { struct in_addr ip_addr; inet_aton(ip.c_str(), &ip_addr); // in network byte order return ip_addr.s_addr; } bool mysortfunction(pair_of_subnets_with_key i, pair_of_subnets_with_key j) { return (i.first < j.first); } int main() { patricia_tree_t* lookup_tree; lookup_tree = New_Patricia(32); make_and_lookup(lookup_tree, "46.36.216.0/21"); make_and_lookup(lookup_tree, "159.253.16.0/21"); make_and_lookup(lookup_tree, "5.45.112.0/21"); make_and_lookup(lookup_tree, "5.45.120.0/21"); make_and_lookup(lookup_tree, "5.101.112.0/21"); make_and_lookup(lookup_tree, "5.101.120.0/21"); make_and_lookup(lookup_tree, "185.4.72.0/22"); make_and_lookup(lookup_tree, "181.114.240.0/20"); make_and_lookup(lookup_tree, "193.42.142.0/24"); // patricia_process (lookup_tree, subnet_vectors_allocator); // std::sort(SubnetVectorVector.begin(), SubnetVectorVector.end(), mysortfunction); prefix_t prefix_for_check_adreess; prefix_for_check_adreess.family = AF_INET; prefix_for_check_adreess.bitlen = 32; patricia_node_t* found_patrica_node = NULL; // prefix_for_check_adreess.add.sin.s_addr = 123123123; // std::map lpm_cache; // Without cache: 16.7 million of operations int i_iter = 100; // Million operations int j_iter = 1000000; // printf("Preallocate table\n"); // Iterate over all our IP addresses // for (int j = 0; j < j_iter; j++) { // for (int i = 0; i < i_iter; i++) { // lpm_cache[i*j] = true; // } //} printf("Start tests\n"); timespec start_time; clock_gettime(CLOCK_REALTIME, &start_time); prefix_for_check_adreess.add.sin.s_addr = convert_ip_as_string_to_uint("159.253.17.1"); for (int j = 0; j < j_iter; j++) { for (int i = 0; i < i_iter; i++) { // Random Pseudo IP // prefix_for_check_adreess.add.sin.s_addr = i*j; patricia_node_t* found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); unsigned long destination_subnet = 0; suxx_func(found_patrica_node != NULL); if (found_patrica_node != NULL) { destination_subnet = found_patrica_node->prefix->add.sin.s_addr; suxx_func(destination_subnet); // std::cout<<"*"; /* for (vector_of_vector_counters::iterator it = SubnetVectorVector.begin() ; it != SubnetVectorVector.end(); ++it) { std::cout<first<<","; if (it->first == destination_subnet) { suxx_func(destination_subnet); } } std::cout<<"\n"; */ /* map_of_vector_counters::iterator itr; itr = SubnetVectorMap.find(destination_subnet); if (itr == SubnetVectorMap.end()) { } else { suxx_func(destination_subnet); } */ } // prefix_for_check_adreess.add.sin.s_addr = i*j + 1; // patricia_node_t* found_second_patrica_node = patricia_search_best(lookup_tree, // &prefix_for_check_adreess); // std::map ::iterator itr = lpm_cache.find(i*j); // if (itr != lpm_cache.end()) { // found it! //} else { // cache miss // bool result = patricia_search_best(lookup_tree, &prefix_for_check_adreess) != NULL; // lpm_cache[i*j] = result; // not found! //} } } timespec finish_time; clock_gettime(CLOCK_REALTIME, &finish_time); unsigned long used_seconds = finish_time.tv_sec - start_time.tv_sec; unsigned long total_ops = i_iter * j_iter; float megaops_per_second = (float)total_ops / (float)used_seconds / 1000000; printf("Total time is %d seconds total ops: %d\nMillion of ops per second: %.1f\n", used_seconds, total_ops, megaops_per_second); Destroy_Patricia(lookup_tree); } upstream-fastnetmon/src/a10_plugin/0000755000175000017500000000000015060514305015403 5ustar memeupstream-fastnetmon/src/a10_plugin/README.md0000644000175000017500000001011614230006537016662 0ustar meme# 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. upstream-fastnetmon/src/a10_plugin/tests/0000755000175000017500000000000014230006537016546 5ustar memeupstream-fastnetmon/src/a10_plugin/tests/__init__.py0000644000175000017500000000000014230006537020645 0ustar memeupstream-fastnetmon/src/a10_plugin/tests/README.md0000644000175000017500000000222114230006537020022 0ustar meme## 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$ ``` upstream-fastnetmon/src/a10_plugin/tests/helperTests.py0000644000175000017500000000251414230006537021424 0ustar memeimport 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() upstream-fastnetmon/src/a10_plugin/change_log.txt0000644000175000017500000000064414230006537020237 0ustar memeChange 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 upstream-fastnetmon/src/a10_plugin/.gitignore0000644000175000017500000000004214230006537017370 0ustar meme*.pyc *.python *.egg *.egg-info/ upstream-fastnetmon/src/a10_plugin/a10.py0000755000175000017500000000307014230006537016342 0ustar meme # # 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 upstream-fastnetmon/src/a10_plugin/json_configs/0000755000175000017500000000000014230006537020065 5ustar memeupstream-fastnetmon/src/a10_plugin/json_configs/ddos_dst_zone.py0000644000175000017500000002524414230006537023304 0ustar meme 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 upstream-fastnetmon/src/a10_plugin/json_configs/__init__.py0000644000175000017500000000000014230006537022164 0ustar memeupstream-fastnetmon/src/a10_plugin/json_configs/tps_base_config_json_v1.txt0000644000175000017500000001017614230006537025417 0ustar memea10-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 } ], } } upstream-fastnetmon/src/a10_plugin/json_configs/tps_zone_config_json_v1.txt0000644000175000017500000002505514230006537025462 0ustar memea10-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, } ] } } ] } upstream-fastnetmon/src/a10_plugin/json_configs/bgp.py0000644000175000017500000000052714230006537021213 0ustar memebgp_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 upstream-fastnetmon/src/a10_plugin/json_configs/write_memory.py0000644000175000017500000000005214230006537023156 0ustar memewrite_mem_path = '/axapi/v3/write/memory' upstream-fastnetmon/src/a10_plugin/json_configs/ddos_dst_zone_backup.py0000644000175000017500000000211714230006537024623 0ustar meme 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 upstream-fastnetmon/src/a10_plugin/json_configs/logoff.py0000644000175000017500000000004314230006537021710 0ustar meme logoff_path = '/axapi/v3/logoff' upstream-fastnetmon/src/a10_plugin/configs/0000755000175000017500000000000014230006537017034 5ustar memeupstream-fastnetmon/src/a10_plugin/configs/README.md0000644000175000017500000000226414230006537020317 0ustar meme# 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. upstream-fastnetmon/src/a10_plugin/configs/tps_zone_config_v1.txt0000644000175000017500000000636214230006537023400 0ustar memeddos 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 upstream-fastnetmon/src/a10_plugin/configs/tps_base_config_v1.txt0000644000175000017500000000275014230006537023334 0ustar memesystem 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 upstream-fastnetmon/src/a10_plugin/fastnetmon_a10_v0.3.py0000775000175000017500000000471315060514305021354 0ustar meme#!/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) upstream-fastnetmon/src/actions/0000755000175000017500000000000015060514305015104 5ustar memeupstream-fastnetmon/src/actions/exabgp_action.cpp0000664000175000017500000000442015060514305020415 0ustar meme#include "exabgp_action.hpp" #include #include "../fast_library.hpp" #include "../all_logcpp_libraries.hpp" extern bool exabgp_enabled; extern std::string exabgp_community; extern std::string exabgp_community_subnet; extern std::string exabgp_community_host; extern std::string exabgp_command_pipe; extern std::string exabgp_next_hop; extern bool exabgp_announce_host; extern bool exabgp_announce_whole_subnet; extern log4cpp::Category& logger; // Low level ExaBGP ban management void exabgp_prefix_ban_manage(std::string action, std::string prefix_as_string_with_mask, std::string exabgp_next_hop, std::string exabgp_community) { /* Buffer for BGP message */ char bgp_message[256]; if (action == "ban") { sprintf(bgp_message, "announce route %s next-hop %s community %s\n", prefix_as_string_with_mask.c_str(), exabgp_next_hop.c_str(), exabgp_community.c_str()); } else { sprintf(bgp_message, "withdraw route %s next-hop %s\n", prefix_as_string_with_mask.c_str(), exabgp_next_hop.c_str()); } logger << log4cpp::Priority::INFO << "ExaBGP announce message: " << bgp_message; int exabgp_pipe = open(exabgp_command_pipe.c_str(), O_WRONLY); if (exabgp_pipe <= 0) { logger << log4cpp::Priority::ERROR << "Can't open ExaBGP pipe " << exabgp_command_pipe << " Ban is not executed"; return; } int wrote_bytes = write(exabgp_pipe, bgp_message, strlen(bgp_message)); if (wrote_bytes != strlen(bgp_message)) { logger << log4cpp::Priority::ERROR << "Can't write message to ExaBGP pipe"; } close(exabgp_pipe); } void exabgp_ban_manage(const std::string& action, const std::string& ip_as_string, const subnet_cidr_mask_t& customer_network) { // We will announce whole subnet here if (exabgp_announce_whole_subnet) { std::string subnet_as_string_with_mask = convert_subnet_to_string(customer_network); exabgp_prefix_ban_manage(action, subnet_as_string_with_mask, exabgp_next_hop, exabgp_community_subnet); } // And we could announce single host here (/32) if (exabgp_announce_host) { std::string ip_as_string_with_mask = ip_as_string + "/32"; exabgp_prefix_ban_manage(action, ip_as_string_with_mask, exabgp_next_hop, exabgp_community_host); } } upstream-fastnetmon/src/actions/gobgp_action.hpp0000664000175000017500000000063415060514305020255 0ustar meme#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); upstream-fastnetmon/src/actions/exabgp_action.hpp0000664000175000017500000000026615060514305020426 0ustar meme#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); upstream-fastnetmon/src/actions/gobgp_action.cpp0000664000175000017500000003261415060514305020253 0ustar meme#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); } } upstream-fastnetmon/src/fastnetmon_api_client.cpp0000664000175000017500000001016115060514305020516 0ustar meme#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; } upstream-fastnetmon/src/libpatricia/0000755000175000017500000000000015060514305015727 5ustar memeupstream-fastnetmon/src/libpatricia/patricia.cpp0000664000175000017500000004714715060514305020246 0ustar meme/* * $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; } upstream-fastnetmon/src/libpatricia/copyright0000644000175000017500000000234514230006537017667 0ustar meme$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.upstream-fastnetmon/src/libpatricia/patricia.hpp0000664000175000017500000000637215060514305020246 0ustar meme/* * $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); upstream-fastnetmon/src/libpatricia/credits.txt0000644000175000017500000000543514230006537020135 0ustar meme [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 upstream-fastnetmon/src/fastnetmon_simple_packet.hpp0000664000175000017500000000666415060514305021251 0ustar meme#pragma once #include #ifdef _WIN32 #include #include // in6_addr #else #include // in6_addr #include #endif #include enum direction_t { INCOMING = 0, OUTGOING = 1, INTERNAL = 2, OTHER = 3 }; enum source_t { UNKNOWN = 0, MIRROR = 1, SFLOW = 2, NETFLOW = 3, TERAFLOW = 4 }; // Forwarding status of packet // IPFIX: https://datatracker.ietf.org/doc/html/rfc7270#section-4.12 // Netflow v9: https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html enum class forwarding_status_t { unknown, forwarded, dropped, consumed }; // Our internal representation of all packet types class simple_packet_t { public: // Source plugin for this traffic type source_t source = UNKNOWN; // Sampling rate uint32_t sample_ratio = 1; // IPv4 in big endian, network byte order uint32_t src_ip = 0; uint32_t dst_ip = 0; // IPv6 addresses in6_addr src_ipv6{}; in6_addr dst_ipv6{}; uint8_t source_mac[6]{}; uint8_t destination_mac[6]{}; // ASNs uint32_t src_asn = 0; uint32_t dst_asn = 0; // Countries // These strings are statically allocated and do not use dynamic memory boost::beast::static_string<2> src_country; boost::beast::static_string<2> dst_country; // Physical port numbers from network equipment uint32_t input_interface = 0; uint32_t output_interface = 0; // IP protocol version: IPv4 or IPv6 uint8_t ip_protocol_version = 4; uint8_t ttl = 0; uint16_t source_port = 0; uint16_t destination_port = 0; uint32_t protocol = 0; uint64_t length = 0; // The number of octets includes IP header(s) and IP payload. // We use it in addition to length because flow spec rule needs exactly it uint64_t ip_length = 0; // Any single simple flow may have multiple packets. It happens for all flow based protocols uint64_t number_of_packets = 1; // TCP flags uint8_t flags = 0; // If IP packet fragmented bool ip_fragmented = false; // We will have more fragments bool ip_more_fragments = false; // If IP has don't fragment flag bool ip_dont_fragment = false; // Fragment offset in bytes when fragmentation involved uint16_t ip_fragment_offset = 0; // Time when we actually received this packet, we use quite rough and inaccurate but very fast time source for it time_t arrival_time = 0; // Timestamp of packet as reported by Netflow or IPFIX agent on device, it may be very inaccurate as nobody cares about time on equipment struct timeval ts = { 0, 0 }; const void* payload_pointer = nullptr; // Part of packet we captured from wire. It may not be full length of packet int32_t captured_payload_length = 0; // Full length of packet we observed. It may be larger then packet_captured_payload_length in case of cropped mirror or sFlow traffic uint32_t payload_full_length = 0; // Forwarding status forwarding_status_t forwarding_status = forwarding_status_t::unknown; // vlan tag if we can extract it uint32_t vlan = 0; // Device uptime when flow started int64_t flow_start = 0; // Device uptime when flow finished int64_t flow_end = 0; direction_t packet_direction = OTHER; // IP address of device which send this flow uint32_t agent_ip_address = 0; }; upstream-fastnetmon/src/api.hpp0000664000175000017500000000352715060514305014737 0ustar meme#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; }; upstream-fastnetmon/src/fastnetmon_types.hpp0000664000175000017500000005510115060514305017563 0ustar meme#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 upstream-fastnetmon/src/fastnetmon.conf0000664000175000017500000002624715060514305016506 0ustar meme### ### 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 upstream-fastnetmon/src/fastnetmon_plugin.hpp0000664000175000017500000000064215060514305017715 0ustar meme#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; upstream-fastnetmon/src/bgp_protocol.hpp0000664000175000017500000007263315060514305016663 0ustar meme#pragma once #include #include #include #include #include #include #include #include "dynamic_binary_buffer.hpp" #include "fast_library.hpp" #include "fastnetmon_networks.hpp" #include "fast_endianless.hpp" #include #include "iana_ip_protocols.hpp" class bgp_attribute_origin; class bgp_attribute_next_hop_ipv4; class IPv4UnicastAnnounce; class IPv6UnicastAnnounce; bool decode_bgp_subnet_encoding_ipv4_raw(uint8_t* value, subnet_cidr_mask_t& extracted_prefix); bool decode_bgp_subnet_encoding_ipv4(int len, uint8_t* value, subnet_cidr_mask_t& extracted_prefix, uint32_t& parsed_nlri_length); uint32_t how_much_bytes_we_need_for_storing_certain_subnet_mask(uint8_t prefix_bit_length); std::string get_bgp_attribute_name_by_number(uint8_t bgp_attribute_type); extern log4cpp::Category& logger; enum BGP_PROTOCOL_MESSAGE_TYPES_UNTYPED : uint8_t { BGP_PROTOCOL_MESSAGE_OPEN = 1, BGP_PROTOCOL_MESSAGE_UPDATE = 2, BGP_PROTOCOL_MESSAGE_NOTIFICATION = 3, BGP_PROTOCOL_MESSAGE_KEEPALIVE = 4, }; // More details here https://tools.ietf.org/html/rfc4271#page-12 class __attribute__((__packed__)) bgp_message_header_t { public: uint8_t marker[16] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; uint16_t length = 0; uint8_t type = 0; void host_byte_order_to_network_byte_order() { length = htons(length); } }; static_assert(sizeof(bgp_message_header_t) == 19, "Broken size for bgp_message_header_t"); // TODO: move to // // https://www.ietf.org/rfc/rfc4271.txt pages 15 and 16 // https://github.com/osrg/gobgp/blob/d6148c75a30d87c3f8c1d0f68725127e4c5f3a65/packet/bgp.go#L3012 struct __attribute__((__packed__)) bgp_attribute_flags { uint8_t : 4, extended_length_bit : 1 = 0, partial_bit : 1 = 0, transitive_bit : 1 = 0, optional_bit : 1 = 0; bgp_attribute_flags(uint8_t flag_as_integer) { memcpy(this, &flag_as_integer, sizeof(flag_as_integer)); } bgp_attribute_flags() { memset(this, 0, sizeof(*this)); } void set_optional_bit(bool bit_value) { optional_bit = (int)bit_value; } void set_transitive_bit(bool bit_value) { transitive_bit = (int)bit_value; } void set_partial_bit(bool bit_value) { partial_bit = (int)bit_value; } void set_extended_length_bit(bool bit_value) { extended_length_bit = (int)bit_value; } bool get_optional_bit() { return optional_bit == 1 ? true : false; } bool get_transitive_bit() { return transitive_bit == 1 ? true : false; } bool get_partial_bit() { return partial_bit == 1 ? true : false; } bool get_extended_length_bit() { return extended_length_bit == 1 ? true : false; } std::string print() const { std::stringstream buf; buf << "optional: " << int(optional_bit) << " transitive: " << int(transitive_bit) << " partial_bit: " << int(partial_bit) << " extended_length_bit: " << int(extended_length_bit); return buf.str(); } }; class bgp_attibute_common_header_t { public: uint8_t attribute_flags = 0; uint8_t attribute_type = 0; uint32_t length_of_length_field = 0; uint32_t attribute_value_length = 0; uint32_t attribute_body_shift = 0; // Just const value uint32_t attribute_flag_and_type_length = 2; std::string print() const { std::stringstream buffer; buffer << "attribute_flags: " << uint32_t(attribute_flags) << " " // << "attribute_pretty_flags: " << // bgp_attribute_flags(attribute_flags).print() << " " << "attribute_type: " << uint32_t(attribute_type) << " " << "attribute_name: " << get_bgp_attribute_name_by_number(attribute_type) << " " << "length_of_length_field: " << length_of_length_field << " " << "attribute_value_length: " << attribute_value_length; return buffer.str(); } // More user friendly form bool parse_raw_bgp_attribute_binary_buffer(dynamic_binary_buffer_t dynamic_binary_buffer) { return parse_raw_bgp_attribute((uint8_t*)dynamic_binary_buffer.get_pointer(), dynamic_binary_buffer.get_used_size()); } bool parse_raw_bgp_attribute(uint8_t* value, size_t len) { if (len < attribute_flag_and_type_length or value == NULL) { logger << log4cpp::Priority::WARN << "Too short attribute. We need least two bytes here but get " << len << " bytes"; return false; } // https://www.ietf.org/rfc/rfc4271.txt page 15 attribute_flags = value[0]; attribute_type = value[1]; bgp_attribute_flags attr_flags(attribute_flags); // attr_flags.print(); length_of_length_field = 1; if (attr_flags.extended_length_bit == 1) { // When we have extended_length_bit we have two bytes for length // information // TODO: add support for this type of attributes // logger << log4cpp::Priority::WARN << "We haven't support for extended // length attributes. // Sorry!" << // std::endl; length_of_length_field = 2; } if (len < attribute_flag_and_type_length + length_of_length_field) { logger << log4cpp::Priority::WARN << "Too short attribute because we need least " << attribute_flag_and_type_length + length_of_length_field << " bytes"; return false; } attribute_value_length = value[2]; // logger << log4cpp::Priority::WARN << "Attribute type: " << // int(attribute_type) ; // logger << log4cpp::Priority::WARN << "Raw attribute length: " << len ; // logger << log4cpp::Priority::WARN << "Attribute internal length: " << // int(attribute_value_length) // ; uint32_t total_attribute_length = attribute_flag_and_type_length + length_of_length_field + attribute_value_length; // logger << log4cpp::Priority::WARN << "attribute_flag_and_type_length: " // << // attribute_flag_and_type_length << // std::endl // << "length_of_length_field: " << length_of_length_field // << "attribute_value_length: " << attribute_value_length ; if (len < total_attribute_length) { logger << log4cpp::Priority::WARN << "Atrribute value length: " << total_attribute_length << " length exceed whole packet length " << len; return false; } // Store shift to attribute payload attribute_body_shift = attribute_flag_and_type_length + length_of_length_field; return true; } }; bool decode_nlri_ipv4(int len, uint8_t* value, subnet_cidr_mask_t& extracted_prefix); bool decode_attribute(int len, char* value, IPv4UnicastAnnounce& unicast_ipv4_announce); bool encode_bgp_subnet_encoding(const subnet_cidr_mask_t& prefix, dynamic_binary_buffer_t& buffer_result); std::string get_origin_name_by_value(uint8_t origin_value); const unsigned int AFI_IP = 1; const unsigned int AFI_IP6 = 2; const unsigned int SAFI_UNICAST = 1; const unsigned int SAFI_FLOW_SPEC_UNICAST = 133; const unsigned int ipv4_unicast_route_family = AFI_IP << 16 | SAFI_UNICAST; const unsigned int ipv4_flow_spec_route_family = AFI_IP << 16 | SAFI_FLOW_SPEC_UNICAST; const unsigned int ipv6_unicast_route_family = AFI_IP6 << 16 | SAFI_UNICAST; const unsigned int ipv6_flow_spec_route_family = AFI_IP6 << 16 | SAFI_FLOW_SPEC_UNICAST; // http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml // https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#transitive enum EXTENDED_COMMUNITY_TYPES_HIGHT_UNTYPED : uint8_t { // Transitive IPv4-Address-Specific Extended Community (Sub-Types are defined in the "Transitive IPv4-Address-Specific Extended Community Sub-Types" registry) EXTENDED_COMMUNITY_TRANSITIVE_IPV4_ADDRESS_SPECIFIC = 1, // 0x01 // We are encoding attributes for BGP flow spec this way // Generic Transitive Experimental Use Extended Community EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL = 128, // 0x80 }; // Subtypes for EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL // http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml // https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#transitive enum EXTENDED_COMMUNITY_TYPES_LOW_FOR_COMMUNITY_TRANSITIVE_EXPEREMENTAL : uint8_t { FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_RATE = 6, FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_ACTION = 7, FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_REDIRECT_AS_TWO_BYTE = 8, FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_REMARKING = 9, }; enum BGP_ATTRIBUTES_TYPES : uint8_t { // https://www.ietf.org/rfc/rfc4271.txt from page 17 BGP_ATTRIBUTE_ORIGIN = 1, BGP_ATTRIBUTE_AS_PATH = 2, BGP_ATTRIBUTE_NEXT_HOP = 3, BGP_ATTRIBUTE_MULTI_EXIT_DISC = 4, BGP_ATTRIBUTE_LOCAL_PREF = 5, BGP_ATTRIBUTE_ATOMIC_AGGREGATE = 6, BGP_ATTRIBUTE_AGGREGATOR = 7, // https://tools.ietf.org/html/rfc1997 from page 1 BGP_ATTRIBUTE_COMMUNITY = 8, // https://tools.ietf.org/html/rfc4760 from page 2 BGP_ATTRIBUTE_MP_REACH_NLRI = 14, // https://tools.ietf.org/html/rfc4360 from page 1 BGP_ATTRIBUTE_EXTENDED_COMMUNITY = 16, }; // https://www.ietf.org/rfc/rfc4271.txt from page 17 enum BGP_ORIGIN_TYPES : uint8_t { BGP_ORIGIN_IGP = 0, BGP_ORIGIN_EGP = 1, BGP_ORIGIN_INCOMPLETE = 2 }; // https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#trans-ipv4 enum BGP_IPV4_EXTENDED_COMMUNITY_SUBTYPES_TRANSITIVE : uint8_t { BGP_IPV4_EXTENDED_COMMUNITY_SUBTYPE_FLOW_SPEC_REDIRECT_IPv4 = 0x0c, }; // It's short MP reach NLRI header. We use it in case when we have non zero length for length_of_next_hop // I use it only to decode/encode IPv6 annnounces class __attribute__((__packed__)) bgp_mp_reach_short_header_t { public: uint16_t afi_identifier = AFI_IP6; uint8_t safi_identifier = SAFI_UNICAST; // According to https://www.ietf.org/rfc/rfc2545.txt we should set it to 16 for global IP addresses uint8_t length_of_next_hop = 16; void network_to_host_byte_order() { afi_identifier = ntohs(afi_identifier); } void host_byte_order_to_network_byte_order() { afi_identifier = htons(afi_identifier); } std::string print() const { std::stringstream buffer; buffer << "afi_identifier: " << uint32_t(afi_identifier) << " " << "safi_identifier: " << uint32_t(safi_identifier) << " " << "length_of_next_hop: " << uint32_t(length_of_next_hop); return buffer.str(); } }; class __attribute__((__packed__)) bgp_attribute_community_t { public: bgp_attribute_community_t() { attribute_type = BGP_ATTRIBUTE_COMMUNITY; // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_optional_bit(true); attribute_flags.set_extended_length_bit(false); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = 0; // This variable store total size in bytes of all community elements (each // element should has // size 4 bytes) uint8_t attribute_length = 0; // Here we have multiple elements of type: bgp_community_attribute_element_t }; // Format of AS_PATH element described here: https://www.rfc-editor.org/rfc/rfc4271.html#page-17 // And with 32 bit / 4 byte ASN corrections from here: https://www.rfc-editor.org/rfc/rfc6793 page 2 // The AS path information exchanged between NEW BGP speakers is carried in the existing AS_PATH attribute, // except that each AS number in the attribute is encoded as a four-octet entity (instead of a two-octet entity). class __attribute__((__packed__)) bgp_as_path_segment_element_t { public: // Path segment type declares on of two possible types of segments: // 1 AS_SET: unordered set of ASes a route in the UPDATE message has traversed // 2 AS_SEQUENCE: ordered set of ASes a route in the UPDATE message has traversed // We use AS_SEQUENCE as default option uint8_t path_segment_type = 2; // The path segment length is a 1-octet length field, containing the number of ASes (not the number of octets) in // the path segment value field. uint8_t path_segment_length = 0; // Here we store list of 4 byte ASNs encoded in 4 byte format each }; // BGP attribute AS_PATH // In RFC this encoding format is covered here: https://www.rfc-editor.org/rfc/rfc4271.html#page-18 class __attribute__((__packed__)) bgp_attribute_as_path_t { public: bgp_attribute_as_path_t() { attribute_type = BGP_ATTRIBUTE_AS_PATH; // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); // It's mandatory: https://www.rfc-editor.org/rfc/rfc4271.html#section-5.1.2 attribute_flags.set_optional_bit(false); // Means that we using single octet length field encoding: // https://www.rfc-editor.org/rfc/rfc4271.html#page-17 attribute_flags.set_extended_length_bit(false); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = 0; // This variable store total size in bytes of all AS_PATH elements uint8_t attribute_length = 0; // Here we have multiple elements of type: bgp_as_path_segment_element_t }; // This is new extended communities: // More details: https://tools.ietf.org/html/rfc4360 class __attribute__((__packed__)) bgp_extended_community_attribute_t { public: bgp_extended_community_attribute_t() { // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_optional_bit(true); attribute_flags.set_extended_length_bit(false); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = BGP_ATTRIBUTE_EXTENDED_COMMUNITY; // This variable store total size in bytes of all community elements. Each // community element has // size 8 bytes uint8_t attribute_length = 0; // Here we have multiple elements of type: bgp_extended_community_element_t }; // BGP extended community attribute class __attribute__((__packed__)) bgp_extended_community_element_t { public: uint8_t type_hight = 0; uint8_t type_low = 0; // This data depends on implementation and values in type_* variables uint8_t value[6] = { 0, 0, 0, 0, 0, 0 }; std::string print() const { std::stringstream buffer; buffer << "type hight: " << uint32_t(type_hight) << " " << "type low: " << uint32_t(type_low) << " " << "value raw: " << print_binary_string_as_hex_with_leading_0x(value, sizeof(value)); return buffer.str(); } }; static_assert(sizeof(bgp_extended_community_element_t) == 8, "Bad size for bgp_extended_community_element_t"); // This is class for storing old style BGP communities which support only 16 // bit AS numbers class __attribute__((__packed__)) bgp_community_attribute_element_t { public: uint16_t asn_number = 0; uint16_t community_number = 0; void host_byte_order_to_network_byte_order() { asn_number = htons(asn_number); community_number = htons(community_number); } }; bool read_bgp_community_from_string(std::string community_as_string, bgp_community_attribute_element_t& bgp_community_attribute_element); static_assert(sizeof(bgp_community_attribute_element_t) == 4, "Broken size of bgp_community_attribute_element_t"); // With this attribute we are encoding different things (flow spec for example) class __attribute__((__packed__)) bgp_attribute_multiprotocol_extensions_t { public: bgp_attribute_multiprotocol_extensions_t() { attribute_flags.set_transitive_bit(false); attribute_flags.set_optional_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_extended_length_bit(false); } std::string print() const { std::stringstream buf; buf << attribute_flags.print() << "attribute type: " << int(attribute_type) << " attribute_length: " << int(attribute_length); return buf.str(); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = BGP_ATTRIBUTE_MP_REACH_NLRI; uint8_t attribute_length = 0; // value has very complex format and we are encoding it with external code }; class __attribute__((__packed__)) bgp_attribute_origin { public: bgp_attribute_origin() { // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_optional_bit(false); attribute_flags.set_extended_length_bit(false); } std::string print() const { std::stringstream buf; buf << attribute_flags.print() << "attribute type: " << int(attribute_type) << " attribute_length: " << int(attribute_length) << " attribute_value: " << attribute_value; return buf.str(); } bgp_attribute_flags attribute_flags; uint8_t attribute_type = BGP_ATTRIBUTE_ORIGIN; uint8_t attribute_length = 1; uint8_t attribute_value = (uint8_t)BGP_ORIGIN_INCOMPLETE; }; class __attribute__((__packed__)) bgp_attribute_next_hop_ipv4 { public: bgp_attribute_next_hop_ipv4() { // Set attribute flags attribute_flags.set_transitive_bit(true); attribute_flags.set_partial_bit(false); attribute_flags.set_optional_bit(false); attribute_flags.set_extended_length_bit(false); } bgp_attribute_next_hop_ipv4(uint32_t next_hop) : bgp_attribute_next_hop_ipv4() { attribute_value = next_hop; } bgp_attribute_flags attribute_flags; uint8_t attribute_type = BGP_ATTRIBUTE_NEXT_HOP; uint8_t attribute_length = 4; uint32_t attribute_value = 0; std::string print() const { std::stringstream buf; buf << attribute_flags.print() << "attribute type: " << int(attribute_type) << " attribute_length: " << int(attribute_length) << " attribute_value: " << attribute_value; return buf.str(); } }; class IPv4UnicastAnnounce { public: void set_withdraw(bool withdraw) { this->is_withdraw = withdraw; } bool get_withdraw() { return this->is_withdraw; } void set_next_hop(uint32_t next_hop) { this->next_hop = next_hop; } void set_prefix(subnet_cidr_mask_t prefix) { this->prefix = prefix; } void set_origin(BGP_ORIGIN_TYPES origin) { this->origin = origin; } uint32_t get_next_hop() { return next_hop; } subnet_cidr_mask_t get_prefix() { return this->prefix; } BGP_ORIGIN_TYPES get_origin() { return this->origin; } // Returns prefix in text form std::string get_prefix_in_cidr_form() { return convert_ip_as_uint_to_string(prefix.subnet_address) + "/" + std::to_string(prefix.cidr_prefix_length); } bool generate_nlri(dynamic_binary_buffer_t& buffer_result) const { return encode_bgp_subnet_encoding(this->prefix, buffer_result); } std::vector get_attributes() const { /* * The sender of an UPDATE message SHOULD order path attributes within * the UPDATE message in ascending order of attribute type. The * receiver of an UPDATE message MUST be prepared to handle path * attributes within UPDATE messages that are out of order. */ bgp_attribute_origin origin_attr; // logger << log4cpp::Priority::WARN << "origin_attr: " << // origin_attr.print() << // std::endl; dynamic_binary_buffer_t origin_as_binary_array; origin_as_binary_array.set_maximum_buffer_size_in_bytes(sizeof(origin_attr)); origin_as_binary_array.append_data_as_object_ptr(&origin_attr); bgp_attribute_next_hop_ipv4 next_hop_attr(next_hop); // logger << log4cpp::Priority::WARN << "next_hop_attr: " << // next_hop_attr.print() << // std::endl; dynamic_binary_buffer_t next_hop_as_binary_array; next_hop_as_binary_array.set_maximum_buffer_size_in_bytes(sizeof(next_hop_attr)); next_hop_as_binary_array.append_data_as_object_ptr(&next_hop_attr); // Vector should be ordered in ascending order of attribute types // Check numbers in enum BGP_ATTRIBUTES_TYPES for information std::vector attributes_list; // It has attribute #1 and will be first in all the cases attributes_list.push_back(origin_as_binary_array); // AS Path should be here and it's #2 if (this->as_path_asns.size() > 0) { // We have ASNs for AS_PATH attribute bgp_attribute_as_path_t bgp_attribute_as_path; // Populate attribute length bgp_attribute_as_path.attribute_length = sizeof(bgp_as_path_segment_element_t) + this->as_path_asns.size() * sizeof(uint32_t); logger << log4cpp::Priority::DEBUG << "AS_PATH attribute length: " << uint32_t(bgp_attribute_as_path.attribute_length); uint32_t as_path_attribute_full_length = sizeof(bgp_attribute_as_path_t) + bgp_attribute_as_path.attribute_length; logger << log4cpp::Priority::DEBUG << "AS_PATH attribute full length: " << as_path_attribute_full_length; dynamic_binary_buffer_t as_path_as_binary_array; as_path_as_binary_array.set_maximum_buffer_size_in_bytes(as_path_attribute_full_length); // Append attribute header as_path_as_binary_array.append_data_as_object_ptr(&bgp_attribute_as_path); bgp_as_path_segment_element_t bgp_as_path_segment_element; // Numbers of ASNs in list bgp_as_path_segment_element.path_segment_length = this->as_path_asns.size(); logger << log4cpp::Priority::DEBUG << "AS_PATH segments number: " << uint32_t(bgp_as_path_segment_element.path_segment_length); // Append segment header as_path_as_binary_array.append_data_as_object_ptr(&bgp_as_path_segment_element); logger << log4cpp::Priority::DEBUG << "AS_PATH ASN 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 attributes_list; } else { // We have communities bgp_attribute_community_t bgp_attribute_community; // Each record has this of 4 bytes bgp_attribute_community.attribute_length = community_list.size() * sizeof(bgp_community_attribute_element_t); uint32_t community_attribute_full_length = sizeof(bgp_attribute_community_t) + bgp_attribute_community.attribute_length; dynamic_binary_buffer_t communities_list_as_binary_array; communities_list_as_binary_array.set_maximum_buffer_size_in_bytes(community_attribute_full_length); communities_list_as_binary_array.append_data_as_object_ptr(&bgp_attribute_community); for (auto bgp_community_element : community_list) { // Encode they in network byte order bgp_community_element.host_byte_order_to_network_byte_order(); communities_list_as_binary_array.append_data_as_object_ptr(&bgp_community_element); } // Community is attribute #8 attributes_list.push_back(communities_list_as_binary_array); return attributes_list; } } std::string print() const { std::stringstream buf; buf << "Prefix: " << convert_ip_as_uint_to_string(prefix.subnet_address) << "/" << prefix.cidr_prefix_length << " " << "Origin: " << get_origin_name_by_value(origin) << " " << "Next hop: " << convert_ip_as_uint_to_string(next_hop) + "/32"; // TODO: print pretty communities!!! return buf.str(); } // Add multiple communities in single step bool add_multiple_communities(std::vector bgp_communities) { for (auto bgp_community : bgp_communities) { community_list.push_back(bgp_community); } return true; } bool add_community(bgp_community_attribute_element_t bgp_community) { community_list.push_back(bgp_community); return true; } // Add ASN to AS_PATH void add_asn_as_path(uint32_t asn) { as_path_asns.push_back(asn); } // Sets AS_PATH to specified vector of uint32_t void set_as_path(std::vector& as_path_asns_param) { as_path_asns = as_path_asns_param; } private: uint32_t next_hop = 0; subnet_cidr_mask_t prefix; BGP_ORIGIN_TYPES origin = BGP_ORIGIN_INCOMPLETE; bool is_withdraw = false; std::vector community_list; // List of ASNs in 32 bit format. May be empty std::vector as_path_asns; }; class IPv6UnicastAnnounce { public: void set_withdraw(bool withdraw) { this->is_withdraw = withdraw; } bool get_withdraw() { return this->is_withdraw; } void set_next_hop(subnet_ipv6_cidr_mask_t next_hop) { this->next_hop = next_hop; } void set_prefix(subnet_ipv6_cidr_mask_t prefix) { this->prefix = prefix; } void set_origin(BGP_ORIGIN_TYPES origin) { this->origin = origin; } subnet_ipv6_cidr_mask_t get_next_hop() const { return next_hop; } subnet_ipv6_cidr_mask_t get_prefix() const { return this->prefix; } BGP_ORIGIN_TYPES get_origin() const { return this->origin; } std::string print() const { std::stringstream buf; buf << "Prefix: " << convert_ipv6_subnet_to_string(prefix) << " " << "Origin: " << get_origin_name_by_value(origin) << " " << "Next hop: " << convert_ipv6_subnet_to_string(next_hop); return buf.str(); } // Returns prefix in text form std::string get_prefix_in_cidr_form() const { return convert_ipv6_subnet_to_string(prefix); } // Add multiple communities in single step bool add_multiple_communities(std::vector bgp_communities) { for (auto bgp_community : bgp_communities) { community_list.push_back(bgp_community); } return true; } bool add_community(bgp_community_attribute_element_t bgp_community) { community_list.push_back(bgp_community); return true; } std::vector get_communities() const { return community_list; } // Add ASN to AS_PATH void add_asn_as_path(uint32_t asn) { as_path_asns.push_back(asn); } // Sets AS_PATH to specified vector of uint32_t void set_as_path(std::vector& as_path_asns_param) { as_path_asns = as_path_asns_param; } private: subnet_ipv6_cidr_mask_t next_hop{}; subnet_ipv6_cidr_mask_t prefix{}; BGP_ORIGIN_TYPES origin = BGP_ORIGIN_INCOMPLETE; bool is_withdraw = false; std::vector community_list; // TODO: we need to rework AnnounceUnicastPrefixLowLevelIPv6 // and pull all logic from it into get_attributes() right here public: // List of ASNs in 32 bit format. May be empty std::vector as_path_asns; }; static_assert(sizeof(bgp_attribute_flags) == 1, "broken size for bgp_attribute_flags"); static_assert(sizeof(bgp_attribute_origin) == 4, "Bad size for bgp_attribute_origin"); static_assert(sizeof(bgp_attribute_next_hop_ipv4) == 7, "Bad size for bgp_attribute_next_hop_ipv4"); bool is_bgp_community_valid(std::string community_as_string); bool decode_ipv6_announce_from_binary_encoded_atributes(std::vector binary_attributes, IPv6UnicastAnnounce& ipv6_announce); bool decode_mp_reach_ipv6(int len, uint8_t* value, bgp_attibute_common_header_t bgp_attibute_common_header, IPv6UnicastAnnounce& ipv6_announce); bool encode_ipv6_announces_into_bgp_mp_reach_attribute_internal(const IPv6UnicastAnnounce& ipv6_announce, dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute); bool encode_ipv6_announces_into_bgp_mp_reach_attribute(const IPv6UnicastAnnounce& ipv6_announce, dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute); bool encode_ipv6_prefix(const subnet_ipv6_cidr_mask_t& prefix, dynamic_binary_buffer_t& dynamic_buffer); upstream-fastnetmon/src/iana_ip_protocols.hpp0000664000175000017500000004446515060514305017700 0ustar meme#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); upstream-fastnetmon/src/gobgp_client/0000775000175000017500000000000015060514305016102 5ustar memeupstream-fastnetmon/src/gobgp_client/gobgp_client.hpp0000664000175000017500000000275315060514305021256 0ustar meme#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_; }; upstream-fastnetmon/src/gobgp_client/gobgp.proto0000664000175000017500000006710015060514305020271 0ustar meme// 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; } upstream-fastnetmon/src/gobgp_client/gobgp_client.cpp0000664000175000017500000002507315060514305021251 0ustar meme#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; } auto bgp_attributes = unicast_ipv4_announce.get_attributes(); 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_maximum_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_maximum_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_maximum_buffer_size_in_bytes(as_path_attribute_full_length); // Append attribute header as_path_as_binary_array.append_data_as_object_ptr(&bgp_attribute_as_path); bgp_as_path_segment_element_t bgp_as_path_segment_element; // Numbers of ASNs in list bgp_as_path_segment_element.path_segment_length = 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_maximum_buffer_size_in_bytes(community_attribute_full_length); communities_list_as_binary_array.append_data_as_object_ptr(&bgp_attribute_community); for (auto bgp_community_element : community_list) { // Encode they in network byte order bgp_community_element.host_byte_order_to_network_byte_order(); communities_list_as_binary_array.append_data_as_object_ptr(&bgp_community_element); } // Community is attribute #8 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); } upstream-fastnetmon/src/gobgp_client/attribute.proto0000664000175000017500000005207715060514305021205 0ustar meme// 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; } upstream-fastnetmon/src/fastnetmon.service.in0000664000175000017500000000073415060514305017617 0ustar meme[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 upstream-fastnetmon/src/filter.cpp0000664000175000017500000002251115060514305015440 0ustar meme#include "filter.hpp" #include "iana_ip_protocols.hpp" #include "ip_lookup_tree.hpp" // Filter packet if it matches list of active flow spec announces bool filter_packet_by_flowspec_rule_list(const simple_packet_t& current_packet, const std::vector& active_flow_spec_announces) { for (auto& flow_announce : active_flow_spec_announces) { // Check that any rule matches specific flow spec announce if (filter_packet_by_flowspec_rule(current_packet, flow_announce)) { return true; } } return false; } // Returns true when packet matches specific rule bool filter_packet_by_flowspec_rule(const simple_packet_t& current_packet, const flow_spec_rule_t& flow_announce) { bool source_port_matches = false; bool destination_port_matches = false; bool source_ip_matches = false; bool destination_ip_matches = false; bool packet_size_matches = false; bool vlan_matches = false; bool tcp_flags_matches = false; bool fragmentation_flags_matches = false; bool protocol_matches = false; if (flow_announce.source_ports.size() == 0) { source_port_matches = true; } else { if (std::find(flow_announce.source_ports.begin(), flow_announce.source_ports.end(), current_packet.source_port) != flow_announce.source_ports.end()) { // We found this IP in list source_port_matches = true; } } if (flow_announce.destination_ports.size() == 0) { destination_port_matches = true; } else { if (std::find(flow_announce.destination_ports.begin(), flow_announce.destination_ports.end(), current_packet.destination_port) != flow_announce.destination_ports.end()) { // We found this IP in list destination_port_matches = true; } } if (flow_announce.protocols.size() == 0) { protocol_matches = true; } else { if (current_packet.protocol <= 255) { // Convert protocol as number into strictly typed protocol_type ip_protocol_t flow_protocol = get_ip_protocol_enum_type_from_integer(uint8_t(current_packet.protocol)); if (std::find(flow_announce.protocols.begin(), flow_announce.protocols.end(), flow_protocol) != flow_announce.protocols.end()) { protocol_matches = true; } } else { // Protocol cannot exceed 255! } } if (flow_announce.source_subnet_ipv4_used) { subnet_cidr_mask_t src_subnet; if (flow_announce.source_subnet_ipv4.cidr_prefix_length == 32) { src_subnet.set_cidr_prefix_length(32); src_subnet.set_subnet_address(current_packet.src_ip); if (flow_announce.source_subnet_ipv4 == src_subnet) { source_ip_matches = true; } } else { // We build Patricia tree for lookup but it's not very effective from performance perspective // To improve performance you may write native function to check if IP belongs to subnet_cidr_mask_t lookup_tree_32bit_t lookup_tree; lookup_tree.add_subnet(flow_announce.source_subnet_ipv4); if (lookup_tree.lookup_ip(current_packet.src_ip)) { source_ip_matches = true; } } } else if (flow_announce.source_subnet_ipv6_used) { if (flow_announce.source_subnet_ipv6.cidr_prefix_length == 128) { subnet_ipv6_cidr_mask_t src_subnet; src_subnet.set_cidr_prefix_length(128); src_subnet.set_subnet_address(¤t_packet.src_ipv6); if (flow_announce.source_subnet_ipv6 == src_subnet) { source_ip_matches = true; } } else { // We do not support non /128 boundaries yet } } else { source_ip_matches = true; } if (flow_announce.destination_subnet_ipv4_used) { if (flow_announce.destination_subnet_ipv4.cidr_prefix_length == 32) { subnet_cidr_mask_t dst_subnet; dst_subnet.set_cidr_prefix_length(32); dst_subnet.set_subnet_address(current_packet.dst_ip); if (flow_announce.destination_subnet_ipv4 == dst_subnet) { destination_ip_matches = true; } } else { // We build Patricia tree for lookup but it's not very effective from performance perspective // To improve performance you may write native function to check if IP belongs to subnet_cidr_mask_t lookup_tree_32bit_t lookup_tree; lookup_tree.add_subnet(flow_announce.destination_subnet_ipv4); if (lookup_tree.lookup_ip(current_packet.dst_ip)) { destination_ip_matches = true; } } } else if (flow_announce.destination_subnet_ipv6_used) { if (flow_announce.destination_subnet_ipv6.cidr_prefix_length == 128) { subnet_ipv6_cidr_mask_t dst_subnet; dst_subnet.set_cidr_prefix_length(128); dst_subnet.set_subnet_address(¤t_packet.dst_ipv6); if (flow_announce.destination_subnet_ipv6 == dst_subnet) { destination_ip_matches = true; } } else { // We do not support non /128 boundaries yet } } else { destination_ip_matches = true; } if (flow_announce.fragmentation_flags.size() == 0) { fragmentation_flags_matches = true; } else { for (auto& fragmentation_flag : flow_announce.fragmentation_flags) { // TODO: we are using only this two options in detection code but we should cover ALL possible cases! if (fragmentation_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_DONT_FRAGMENT) { if (current_packet.ip_dont_fragment) { fragmentation_flags_matches = true; } } else if (fragmentation_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_IS_A_FRAGMENT) { if (current_packet.ip_fragmented) { fragmentation_flags_matches = true; } } } } if (flow_announce.tcp_flags.size() == 0) { tcp_flags_matches = true; } else { // Convert 8bit representation of flags for this packet into flagset representation for flow spec flow_spec_tcp_flagset_t flagset; uint8t_representation_of_tcp_flags_to_flow_spec(current_packet.flags, flagset); // logger << log4cpp::Priority::WARN <<"Packet's tcp flagset: " << flagset.to_string(); if (std::find(flow_announce.tcp_flags.begin(), flow_announce.tcp_flags.end(), flagset) != flow_announce.tcp_flags.end()) { tcp_flags_matches = true; } } if (flow_announce.packet_lengths.size() == 0) { packet_size_matches = true; } else { // Use matching only if we can convert it to 16 bit value if (current_packet.length <= 65635) { // Convert 64 bit (we need it because netflow) value to 16 bit uint16_t packet_length_uint16 = (uint16_t)current_packet.length; // TODO: it's a bit incorrect to use check against packet length here // because as mentioned below we use total ip length for flow spec rules. // And here we have FULL packet length (including L2, Ethernet header) // But we just added ip_length field and need to check both if (std::find(flow_announce.packet_lengths.begin(), flow_announce.packet_lengths.end(), packet_length_uint16) != flow_announce.packet_lengths.end()) { packet_size_matches = true; } // Flow spec uses length in form "total length of IP packet" and we should check it uint16_t ip_packet_length_uint16 = (uint16_t)current_packet.ip_length; if (std::find(flow_announce.packet_lengths.begin(), flow_announce.packet_lengths.end(), ip_packet_length_uint16) != flow_announce.packet_lengths.end()) { packet_size_matches = true; } } } if (flow_announce.vlans.size() == 0) { vlan_matches = true; } else { if (std::find(flow_announce.vlans.begin(), flow_announce.vlans.end(), current_packet.vlan) != flow_announce.vlans.end()) { vlan_matches = true; } } // Nice thing for debug /* logger << log4cpp::Priority::WARN << "source_port_matches: " << source_port_matches << " " << "destination_port_matches: " << destination_port_matches << " " << "source_ip_matches: " << source_ip_matches << " " << "destination_ip_matches: " << destination_ip_matches << " " << "packet_size_matches: " << packet_size_matches << " " << "tcp_flags_matches: " << tcp_flags_matches << " " << "fragmentation_flags_matches: " << fragmentation_flags_matches << " " << "protocol_matches: " << protocol_matches; */ // Return true only of all parts matched if (source_port_matches && destination_port_matches && source_ip_matches && destination_ip_matches && packet_size_matches && tcp_flags_matches && fragmentation_flags_matches && protocol_matches && vlan_matches) { return true; } return false; } upstream-fastnetmon/src/afpacket_plugin/0000755000175000017500000000000015060514305016600 5ustar memeupstream-fastnetmon/src/afpacket_plugin/afpacket_collector.hpp0000664000175000017500000000043715060514305023143 0ustar meme#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(); upstream-fastnetmon/src/afpacket_plugin/afpacket_collector.cpp0000664000175000017500000005271415060514305023143 0ustar meme#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_ng((u_char*)data_pointer, ppd->tp_snaplen, ppd->tp_snaplen, packet, parser_options); if (result != network_data_stuctures::parser_code_t::success) { // This counter resets for speed calculation every second total_unparsed_packets++; af_packet_packets_unparsed++; logger << log4cpp::Priority::DEBUG << "Cannot parse packet using ng parser: " << parser_code_to_string(result); } else { af_packet_packets_parsed++; afpacket_process_func_ptr(packet); } // Move pointer to next packet ppd = (struct tpacket3_hdr*)((uint8_t*)ppd + ppd->tp_next_offset); } } void read_packets_from_socket(int packet_socket, struct iovec* rd); // Configures socket and starts traffic capture bool setup_socket(std::string interface_name, bool enable_fanout, int fanout_group_id) { // More details here: http://man7.org/linux/man-pages/man7/packet.7.html // We could use SOCK_RAW or SOCK_DGRAM for second argument // SOCK_RAW - raw packets pass from the kernel // SOCK_DGRAM - some amount of processing // Third argument manage ether type of captured packets int packet_socket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (packet_socket == -1) { logger << log4cpp::Priority::ERROR << "Can't create AF_PACKET socket. Error number: " << errno << " error text: " << strerror(errno); return false; } if (true) { // Add socket to global structure, we will use it to get statistics for each of them std::lock_guard lock_guard(active_af_packet_sockets_mutex); active_af_packet_sockets.push_back(packet_socket); } // We should use V3 because it could read/pool in per block basis instead per // packet int version = TPACKET_V3; int setsockopt_packet_version = setsockopt(packet_socket, SOL_PACKET, PACKET_VERSION, &version, sizeof(version)); if (setsockopt_packet_version < 0) { logger << log4cpp::Priority::ERROR << "Can't set packet v3 version for " << interface_name; return false; } int interface_number = 0; bool get_interface_number_result = get_interface_number_by_device_name(packet_socket, interface_name, interface_number); if (!get_interface_number_result) { logger << log4cpp::Priority::ERROR << "Can't get interface number by interface name for " << interface_name; return false; } 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(); } } upstream-fastnetmon/src/fast_library.cpp0000664000175000017500000025337515060514305016652 0ustar meme#include "fast_library.hpp" #include #include // Windows does not use ioctl #ifndef _WIN32 #include #endif #ifndef _WIN32 // For uname function #include #endif #include "all_logcpp_libraries.hpp" #include #include #include #include #include #include #include #include #ifdef ENABLE_CAPNP #include "simple_packet_capnp/simple_packet.capnp.h" #include #include #endif #include #include #include "iana_ip_protocols.hpp" boost::regex regular_expression_cidr_pattern("^\\d+\\.\\d+\\.\\d+\\.\\d+\\/\\d+$"); boost::regex regular_expression_host_pattern("^\\d+\\.\\d+\\.\\d+\\.\\d+$"); // convert string to integer int convert_string_to_integer(std::string line) { return atoi(line.c_str()); } std::string convert_ip_as_uint_to_string(uint32_t ip_as_integer) { struct in_addr ip_addr; ip_addr.s_addr = ip_as_integer; return (std::string)inet_ntoa(ip_addr); } std::string convert_ipv4_subnet_to_string(const subnet_cidr_mask_t& subnet) { std::stringstream buffer; buffer << convert_ip_as_uint_to_string(subnet.subnet_address) << "/" << subnet.cidr_prefix_length; return buffer.str(); } // convert integer to string std::string convert_int_to_string(int value) { std::stringstream out; out << value; return out.str(); } // Converts IP address in cidr form 11.22.33.44/24 to our representation bool convert_subnet_from_string_to_binary_with_cidr_format_safe(const std::string& subnet_cidr, subnet_cidr_mask_t& subnet_cidr_mask) { if (subnet_cidr.empty()) { return false; } // It's not a cidr mask if (!is_cidr_subnet(subnet_cidr)) { return false; } std::vector subnet_as_string; split(subnet_as_string, subnet_cidr, boost::is_any_of("/"), boost::token_compress_on); if (subnet_as_string.size() != 2) { return false; } uint32_t subnet_as_int = 0; bool ip_to_integer_convresion_result = convert_ip_as_string_to_uint_safe(subnet_as_string[0], subnet_as_int); if (!ip_to_integer_convresion_result) { return false; } int cidr = 0; bool ip_conversion_result = convert_string_to_any_integer_safe(subnet_as_string[1], cidr); if (!ip_conversion_result) { return false; } subnet_cidr_mask = subnet_cidr_mask_t(subnet_as_int, cidr); return true; } std::string convert_subnet_to_string(subnet_cidr_mask_t my_subnet) { std::stringstream buffer; buffer << convert_ip_as_uint_to_string(my_subnet.subnet_address) << "/" << my_subnet.cidr_prefix_length; return buffer.str(); } // extract 24 from 192.168.1.1/24 unsigned int get_cidr_mask_from_network_as_string(std::string network_cidr_format) { std::vector subnet_as_string; split(subnet_as_string, network_cidr_format, boost::is_any_of("/"), boost::token_compress_on); if (subnet_as_string.size() != 2) { return 0; } return convert_string_to_integer(subnet_as_string[1]); } std::string print_time_t_in_fastnetmon_format(time_t current_time) { struct tm* timeinfo; char buffer[80]; timeinfo = localtime(¤t_time); strftime(buffer, sizeof(buffer), "%d_%m_%y_%H:%M:%S", timeinfo); return std::string(buffer); } // extract 192.168.1.1 from 192.168.1.1/24 std::string get_net_address_from_network_as_string(std::string network_cidr_format) { std::vector subnet_as_string; split(subnet_as_string, network_cidr_format, boost::is_any_of("/"), boost::token_compress_on); if (subnet_as_string.size() != 2) { return std::string(); } return subnet_as_string[0]; } std::string get_printable_protocol_name(unsigned int protocol) { std::string proto_name; switch (protocol) { case IPPROTO_TCP: proto_name = "tcp"; break; case IPPROTO_UDP: proto_name = "udp"; break; case IPPROTO_ICMP: proto_name = "icmp"; break; default: proto_name = "unknown"; break; } return proto_name; } uint32_t convert_cidr_to_binary_netmask(unsigned int cidr) { // We can do bit shift only for 0 .. 31 bits but we cannot do it in case of 32 bits // Shift for same number of bits as type has is undefined behaviour in C standard: // https://stackoverflow.com/questions/7401888/why-doesnt-left-bit-shift-for-32-bit-integers-work-as-expected-when-used // We will handle this case manually if (cidr == 0) { return 0; } uint32_t binary_netmask = 0xFFFFFFFF; binary_netmask = binary_netmask << (32 - cidr); // We need network byte order at output return htonl(binary_netmask); } bool is_cidr_subnet(std::string subnet) { boost::cmatch what; return regex_match(subnet.c_str(), what, regular_expression_cidr_pattern); } bool is_v4_host(std::string host) { boost::cmatch what; return regex_match(host.c_str(), what, regular_expression_host_pattern); } // check file existence bool file_exists(std::string path) { FILE* check_file = fopen(path.c_str(), "r"); if (check_file) { fclose(check_file); return true; } else { return false; } } bool folder_exists(std::string path) { if (access(path.c_str(), 0) == 0) { struct stat status; stat(path.c_str(), &status); if (status.st_mode & S_IFDIR) { return true; } } return false; } // http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html int timeval_subtract(struct timeval* result, struct timeval* x, struct timeval* y) { /* Perform the carry for the later subtraction by updating y. */ if (x->tv_usec < y->tv_usec) { int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; y->tv_usec -= 1000000 * nsec; y->tv_sec += nsec; } if (x->tv_usec - y->tv_usec > 1000000) { int nsec = (x->tv_usec - y->tv_usec) / 1000000; y->tv_usec += 1000000 * nsec; y->tv_sec -= nsec; } /* Compute the time remaining to wait. tv_usec is certainly positive. */ result->tv_sec = x->tv_sec - y->tv_sec; result->tv_usec = x->tv_usec - y->tv_usec; /* Return 1 if result is negative. */ return x->tv_sec < y->tv_sec; } std::string print_tcp_flags(uint8_t flag_value) { if (flag_value == 0) { return "-"; } /* // Required for decoding tcp flags #define TH_FIN_MULTIPLIER 0x01 #define TH_SYN_MULTIPLIER 0x02 #define TH_RST_MULTIPLIER 0x04 #define TH_PUSH_MULTIPLIER 0x08 #define TH_ACK_MULTIPLIER 0x10 #define TH_URG_MULTIPLIER 0x20 */ std::vector all_flags; if (extract_bit_value(flag_value, TCP_FIN_FLAG_SHIFT)) { all_flags.push_back("fin"); } if (extract_bit_value(flag_value, TCP_SYN_FLAG_SHIFT)) { all_flags.push_back("syn"); } if (extract_bit_value(flag_value, TCP_RST_FLAG_SHIFT)) { all_flags.push_back("rst"); } if (extract_bit_value(flag_value, TCP_PSH_FLAG_SHIFT)) { all_flags.push_back("psh"); } if (extract_bit_value(flag_value, TCP_ACK_FLAG_SHIFT)) { all_flags.push_back("ack"); } if (extract_bit_value(flag_value, TCP_URG_FLAG_SHIFT)) { all_flags.push_back("urg"); } std::ostringstream flags_as_string; if (all_flags.empty()) { return "-"; } // concatenate all vector elements with comma std::copy(all_flags.begin(), all_flags.end() - 1, std::ostream_iterator(flags_as_string, ",")); // add last element flags_as_string << all_flags.back(); return flags_as_string.str(); } std::vector split_strings_to_vector_by_comma(std::string raw_string) { std::vector splitted_strings; boost::split(splitted_strings, raw_string, boost::is_any_of(","), boost::token_compress_on); return splitted_strings; } // http://stackoverflow.com/questions/14528233/bit-masking-in-c-how-to-get-first-bit-of-a-byte int extract_bit_value(uint8_t num, int bit) { if (bit > 0 && bit <= 8) { return ((num >> (bit - 1)) & 1); } else { return 0; } } // Overloaded version with 16 bit integer support int extract_bit_value(uint16_t num, int bit) { if (bit > 0 && bit <= 16) { return ((num >> (bit - 1)) & 1); } else { return 0; } } int set_bit_value(uint8_t& num, int bit) { if (bit > 0 && bit <= 8) { num = num | 1 << (bit - 1); return 1; } else { return 0; } } int set_bit_value(uint16_t& num, int bit) { if (bit > 0 && bit <= 16) { num = num | 1 << (bit - 1); return 1; } else { return 0; } } int clear_bit_value(uint8_t& num, int bit) { if (bit > 0 && bit <= 8) { num = num & ~(1 << (bit - 1)); return 1; } else { return 0; } } // http://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit-in-c-c int clear_bit_value(uint16_t& num, int bit) { if (bit > 0 && bit <= 16) { num = num & ~(1 << (bit - 1)); return 1; } else { return 0; } } // Encodes simple packet with all fields as separate fields in json format bool serialize_simple_packet_to_json(const simple_packet_t& packet, nlohmann::json& json_packet) { extern log4cpp::Category& logger; std::string protocol_version; std::string source_ip_as_string; std::string destination_ip_as_string; if (packet.ip_protocol_version == 4) { protocol_version = "ipv4"; source_ip_as_string = convert_ip_as_uint_to_string(packet.src_ip); destination_ip_as_string = convert_ip_as_uint_to_string(packet.dst_ip); } else if (packet.ip_protocol_version == 6) { protocol_version = "ipv6"; source_ip_as_string = print_ipv6_address(packet.src_ipv6); destination_ip_as_string = print_ipv6_address(packet.dst_ipv6); } else { protocol_version = "unknown"; } try { // We use arrival_time as traffic telemetry protocols do not provide this time in a reliable manner json_packet["timestamp"] = packet.arrival_time; json_packet["ip_version"] = protocol_version; json_packet["source_ip"] = source_ip_as_string; json_packet["destination_ip"] = destination_ip_as_string; json_packet["source_asn"] = packet.src_asn; json_packet["destination_asn"] = packet.dst_asn; json_packet["source_country"] = country_static_string_to_dynamic_string(packet.src_country); json_packet["destination_country"] = country_static_string_to_dynamic_string(packet.dst_country); json_packet["input_interface"] = packet.input_interface; json_packet["output_interface"] = packet.output_interface; // Add ports for TCP and UDP if (packet.protocol == IPPROTO_TCP or packet.protocol == IPPROTO_UDP) { json_packet["source_port"] = packet.source_port; json_packet["destination_port"] = packet.destination_port; } // Add agent information std::string agent_ip_as_string = convert_ip_as_uint_to_string(packet.agent_ip_address); json_packet["agent_address"] = agent_ip_as_string; if (packet.protocol == IPPROTO_TCP) { std::string tcp_flags = print_tcp_flags(packet.flags); json_packet["tcp_flags"] = tcp_flags; } // Add forwarding status std::string forwarding_status = forwarding_status_to_string(packet.forwarding_status); json_packet["forwarding_status"] = forwarding_status; json_packet["fragmentation"] = packet.ip_fragmented; json_packet["packets"] = packet.number_of_packets; json_packet["length"] = packet.length; json_packet["ip_length"] = packet.ip_length; json_packet["ttl"] = packet.ttl; json_packet["sample_ratio"] = packet.sample_ratio; std::string protocol = get_printable_protocol_name(packet.protocol); json_packet["protocol"] = protocol; } catch (...) { logger << log4cpp::Priority::ERROR << "Exception was triggered in JSON logic in serialize_simple_packet_to_json"; return false; } return true; } std::string print_simple_packet(simple_packet_t packet) { std::stringstream buffer; if (packet.ts.tv_sec == 0) { // Netmap does not generate timestamp for all packets because it's very CPU // intensive operation // But we want pretty attack report and fill it there gettimeofday(&packet.ts, NULL); } buffer << convert_timeval_to_date(packet.ts) << " "; std::string source_ip_as_string = ""; std::string destination_ip_as_string = ""; if (packet.ip_protocol_version == 4) { source_ip_as_string = convert_ip_as_uint_to_string(packet.src_ip); destination_ip_as_string = convert_ip_as_uint_to_string(packet.dst_ip); } else if (packet.ip_protocol_version == 6) { source_ip_as_string = print_ipv6_address(packet.src_ipv6); destination_ip_as_string = print_ipv6_address(packet.dst_ipv6); } else { // WTF? } std::string protocol_name = get_ip_protocol_name_by_number_iana(packet.protocol); // We use lowercase format boost::algorithm::to_lower(protocol_name); buffer << source_ip_as_string << ":" << packet.source_port << " > " << destination_ip_as_string << ":" << packet.destination_port << " protocol: " << protocol_name; // Print flags only for TCP if (packet.protocol == IPPROTO_TCP) { buffer << " flags: " << print_tcp_flags(packet.flags); } buffer << " frag: " << packet.ip_fragmented << " "; buffer << " "; buffer << "packets: " << packet.number_of_packets << " "; buffer << "size: " << packet.length << " bytes "; // We should cast it to integer because otherwise it will be interpreted as char buffer << "ttl: " << unsigned(packet.ttl) << " "; buffer << "sample ratio: " << packet.sample_ratio << " "; buffer << " \n"; return buffer.str(); } std::string convert_timeval_to_date(const timeval& tv) { time_t nowtime = tv.tv_sec; tm* nowtm = localtime(&nowtime); std::ostringstream ss; ss << std::put_time(nowtm, "%F %H:%M:%S"); // Add microseconds // If value is short we will add leading zeros ss << "." << std::setfill('0') << std::setw(6) << tv.tv_usec; return ss.str(); } uint64_t convert_speed_to_mbps(uint64_t speed_in_bps) { return uint64_t((double)speed_in_bps / 1000 / 1000 * 8); } std::string get_protocol_name_by_number(unsigned int proto_number) { struct protoent* proto_ent = getprotobynumber(proto_number); std::string proto_name = proto_ent->p_name; return proto_name; } // Exec command in shell and capture output bool exec(const std::string& cmd, std::vector& output_list, std::string& error_text) { FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) { // We need more details in case of failure error_text = "error code: " + std::to_string(errno) + " error text: " + strerror(errno); return false; } char buffer[256]; while (!feof(pipe)) { if (fgets(buffer, 256, pipe) != NULL) { size_t newbuflen = strlen(buffer); // remove newline at the end if (buffer[newbuflen - 1] == '\n') { buffer[newbuflen - 1] = '\0'; } output_list.push_back(buffer); } } pclose(pipe); return true; } bool print_pid_to_file(pid_t pid, std::string pid_path) { std::ofstream pid_file; pid_file.open(pid_path.c_str(), std::ios::trunc); if (pid_file.is_open()) { pid_file << pid << "\n"; pid_file.close(); return true; } else { return false; } } bool read_pid_from_file(pid_t& pid, std::string pid_path) { std::fstream pid_file(pid_path.c_str(), std::ios_base::in); if (pid_file.is_open()) { pid_file >> pid; pid_file.close(); return true; } else { return false; } } bool store_data_to_graphite(unsigned short int graphite_port, std::string graphite_host, graphite_data_t graphite_data) { // Do not bother Graphite if we do not have any metrics here if (graphite_data.size() == 0) { return true; } int client_sockfd = socket(AF_INET, SOCK_STREAM, 0); if (client_sockfd < 0) { return false; } struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(graphite_port); int pton_result = inet_pton(AF_INET, graphite_host.c_str(), &serv_addr.sin_addr); if (pton_result <= 0) { close(client_sockfd); return false; } int connect_result = connect(client_sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); if (connect_result < 0) { close(client_sockfd); return false; } std::stringstream buffer; time_t current_time = time(NULL); for (graphite_data_t::iterator itr = graphite_data.begin(); itr != graphite_data.end(); ++itr) { buffer << itr->first << " " << itr->second << " " << current_time << "\n"; } std::string buffer_as_string = buffer.str(); int write_result = write(client_sockfd, buffer_as_string.c_str(), buffer_as_string.size()); close(client_sockfd); if (write_result > 0) { return true; } else { return false; } } // Get list of all available interfaces on the server interfaces_list_t get_interfaces_list() { interfaces_list_t interfaces_list; // Format: 1: eth0: < .... boost::regex interface_name_pattern("^\\d+:\\s+(\\w+):.*?$"); std::string error_text; std::vector output_list; bool exec_result = exec("ip -o link show", output_list, error_text); if (!exec_result) { return interfaces_list; } if (output_list.empty()) { return interfaces_list; } for (std::vector::iterator iter = output_list.begin(); iter != output_list.end(); ++iter) { boost::match_results regex_results; if (boost::regex_match(*iter, regex_results, interface_name_pattern)) { // std::cout<<"Interface: "< output_list; bool exec_result = exec("ip address show dev " + interface_name, output_list, error_text); if (!exec_result) { return ip_list; } if (output_list.empty()) { return ip_list; } boost::regex interface_alias_pattern("^\\s+inet\\s+(\\d+\\.\\d+\\.\\d+\\.\\d+).*?$"); // inet 188.40.35.142 for (std::vector::iterator iter = output_list.begin(); iter != output_list.end(); ++iter) { boost::match_results regex_results; if (boost::regex_match(*iter, regex_results, interface_alias_pattern)) { ip_list.push_back(regex_results[1]); // std::cout<<"IP: "< list_of_ignored_interfaces; list_of_ignored_interfaces.push_back("lo"); list_of_ignored_interfaces.push_back("venet0"); interfaces_list_t interfaces_list = get_interfaces_list(); if (interfaces_list.empty()) { return ip_list; } for (interfaces_list_t::iterator iter = interfaces_list.begin(); iter != interfaces_list.end(); ++iter) { std::vector::iterator iter_exclude_list = std::find(list_of_ignored_interfaces.begin(), list_of_ignored_interfaces.end(), *iter); // Skip ignored interface if (iter_exclude_list != list_of_ignored_interfaces.end()) { continue; } // std::cout<<*iter<add.sin.s_addr); return address + "/" + convert_int_to_string(prefix->bitlen); } // It could not be on start or end of the line boost::regex ipv6_address_compression_algorithm("(0000:){2,}"); // Returns true when all octets of IP address are set to zero bool is_zero_ipv6_address(const in6_addr& ipv6_address) { const uint8_t* b = ipv6_address.s6_addr; if (b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0 && b[4] == 0 && b[5] == 0 && b[6] == 0 && b[7] == 0 && b[8] == 0 && b[9] == 0 && b[10] == 0 && b[11] == 0 && b[12] == 0 && b[13] == 0 && b[14] == 0 && b[15] == 0) { return true; } return false; } std::string print_ipv6_address(const in6_addr& ipv6_address) { char buffer[128]; // For short print const uint8_t* b = ipv6_address.s6_addr; sprintf(buffer, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); std::string buffer_string(buffer); // Compress IPv6 address std::string result = boost::regex_replace(buffer_string, ipv6_address_compression_algorithm, ":", boost::format_first_only); return result; } direction_t get_packet_direction_ipv6(patricia_tree_t* lookup_tree, struct in6_addr src_ipv6, struct in6_addr dst_ipv6, subnet_ipv6_cidr_mask_t& subnet) { direction_t packet_direction; bool our_ip_is_destination = false; bool our_ip_is_source = false; prefix_t prefix_for_check_address; prefix_for_check_address.family = AF_INET6; prefix_for_check_address.bitlen = 128; patricia_node_t* found_patrica_node = NULL; prefix_for_check_address.add.sin6 = dst_ipv6; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_address, 1); subnet_ipv6_cidr_mask_t destination_subnet; if (found_patrica_node) { our_ip_is_destination = true; destination_subnet.subnet_address = found_patrica_node->prefix->add.sin6; destination_subnet.cidr_prefix_length = found_patrica_node->prefix->bitlen; } found_patrica_node = NULL; prefix_for_check_address.add.sin6 = src_ipv6; subnet_ipv6_cidr_mask_t source_subnet; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_address, 1); if (found_patrica_node) { our_ip_is_source = true; source_subnet.subnet_address = found_patrica_node->prefix->add.sin6; source_subnet.cidr_prefix_length = found_patrica_node->prefix->bitlen; } if (our_ip_is_source && our_ip_is_destination) { packet_direction = INTERNAL; } else if (our_ip_is_source) { subnet = source_subnet; packet_direction = OUTGOING; } else if (our_ip_is_destination) { subnet = destination_subnet; packet_direction = INCOMING; } else { packet_direction = OTHER; } return packet_direction; } /* Get traffic type: check it belongs to our IPs */ direction_t get_packet_direction(patricia_tree_t* lookup_tree, uint32_t src_ip, uint32_t dst_ip, subnet_cidr_mask_t& subnet) { direction_t packet_direction; bool our_ip_is_destination = false; bool our_ip_is_source = false; prefix_t prefix_for_check_adreess; prefix_for_check_adreess.family = AF_INET; prefix_for_check_adreess.bitlen = 32; patricia_node_t* found_patrica_node = NULL; prefix_for_check_adreess.add.sin.s_addr = dst_ip; subnet_cidr_mask_t destination_subnet; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); if (found_patrica_node) { our_ip_is_destination = true; destination_subnet.subnet_address = found_patrica_node->prefix->add.sin.s_addr; destination_subnet.cidr_prefix_length = found_patrica_node->prefix->bitlen; } found_patrica_node = NULL; prefix_for_check_adreess.add.sin.s_addr = src_ip; subnet_cidr_mask_t source_subnet; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); if (found_patrica_node) { our_ip_is_source = true; source_subnet.subnet_address = found_patrica_node->prefix->add.sin.s_addr; source_subnet.cidr_prefix_length = found_patrica_node->prefix->bitlen; } if (our_ip_is_source && our_ip_is_destination) { packet_direction = INTERNAL; } else if (our_ip_is_source) { subnet = source_subnet; packet_direction = OUTGOING; } else if (our_ip_is_destination) { subnet = destination_subnet; packet_direction = INCOMING; } else { packet_direction = OTHER; } return packet_direction; } std::string get_direction_name(direction_t direction_value) { std::string direction_name; switch (direction_value) { case INCOMING: direction_name = "incoming"; break; case OUTGOING: direction_name = "outgoing"; break; case INTERNAL: direction_name = "internal"; break; case OTHER: direction_name = "other"; break; default: direction_name = "unknown"; break; } return direction_name; } #ifdef __linux__ bool manage_interface_promisc_mode(std::string interface_name, bool switch_on) { extern log4cpp::Category& logger; // We need really any socket for ioctl int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (!fd) { logger << log4cpp::Priority::ERROR << "Can't create socket for promisc mode manager"; return false; } struct ifreq ethreq; memset(ðreq, 0, sizeof(ethreq)); strncpy(ethreq.ifr_name, interface_name.c_str(), IFNAMSIZ); int ioctl_res = ioctl(fd, SIOCGIFFLAGS, ðreq); if (ioctl_res == -1) { logger << log4cpp::Priority::ERROR << "Can't get interface flags"; return false; } bool promisc_enabled_on_device = ethreq.ifr_flags & IFF_PROMISC; if (switch_on) { if (promisc_enabled_on_device) { logger << log4cpp::Priority::INFO << "Interface " << interface_name << " in promisc mode already"; return true; } else { logger << log4cpp::Priority::INFO << "Interface in non promisc mode now, switch it on"; ethreq.ifr_flags |= IFF_PROMISC; int ioctl_res_set = ioctl(fd, SIOCSIFFLAGS, ðreq); if (ioctl_res_set == -1) { logger << log4cpp::Priority::ERROR << "Can't set interface flags"; return false; } return true; } } else { if (!promisc_enabled_on_device) { logger << log4cpp::Priority::INFO << "Interface " << interface_name << " in normal mode already"; return true; } else { logger << log4cpp::Priority::INFO << "Interface in promisc mode now, switch it off"; ethreq.ifr_flags &= ~IFF_PROMISC; int ioctl_res_set = ioctl(fd, SIOCSIFFLAGS, ðreq); if (ioctl_res_set == -1) { logger << log4cpp::Priority::ERROR << "Can't set interface flags"; return false; } return true; } } } #endif std::string serialize_attack_description(const attack_details_t& current_attack) { std::stringstream attack_description; attack_type_t attack_type = detect_attack_type(current_attack); std::string printable_attack_type = get_printable_attack_name(attack_type); attack_description << "Attack type: " << printable_attack_type << "\n" << "Initial attack power: " << current_attack.attack_power << " packets per second\n" << "Peak attack power: " << current_attack.max_attack_power << " packets per second\n" << "Attack direction: " << get_direction_name(current_attack.attack_direction) << "\n" << "Attack protocol: " << get_printable_protocol_name(current_attack.attack_protocol) << "\n"; attack_description << "Total incoming traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.total.in_bytes) << " mbps\n" << "Total outgoing traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.total.out_bytes) << " mbps\n" << "Total incoming pps: " << current_attack.traffic_counters.total.in_packets << " packets per second\n" << "Total outgoing pps: " << current_attack.traffic_counters.total.out_packets << " packets per second\n" << "Total incoming flows: " << current_attack.traffic_counters.in_flows << " flows per second\n" << "Total outgoing flows: " << current_attack.traffic_counters.out_flows << " flows per second\n"; attack_description << "Incoming ip fragmented traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.fragmented.in_bytes) << " mbps\n" << "Outgoing ip fragmented traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.fragmented.out_bytes) << " mbps\n" << "Incoming ip fragmented pps: " << current_attack.traffic_counters.fragmented.in_packets << " packets per second\n" << "Outgoing ip fragmented pps: " << current_attack.traffic_counters.fragmented.out_packets << " packets per second\n" << "Incoming dropped traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.dropped.in_bytes) << " mbps\n" << "Outgoing dropped traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.dropped.out_bytes) << " mbps\n" << "Incoming dropped pps: " << current_attack.traffic_counters.dropped.in_packets << " packets per second\n" << "Outgoing dropped pps: " << current_attack.traffic_counters.dropped.out_packets << " packets per second\n" << "Incoming tcp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.tcp.in_bytes) << " mbps\n" << "Outgoing tcp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.tcp.out_bytes) << " mbps\n" << "Incoming tcp pps: " << current_attack.traffic_counters.tcp.in_packets << " packets per second\n" << "Outgoing tcp pps: " << current_attack.traffic_counters.tcp.out_packets << " packets per second\n" << "Incoming syn tcp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.tcp_syn.in_bytes) << " mbps\n" << "Outgoing syn tcp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.tcp_syn.out_bytes) << " mbps\n" << "Incoming syn tcp pps: " << current_attack.traffic_counters.tcp_syn.in_packets << " packets per second\n" << "Outgoing syn tcp pps: " << current_attack.traffic_counters.tcp_syn.out_packets << " packets per second\n" << "Incoming udp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.udp.in_bytes) << " mbps\n" << "Outgoing udp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.udp.out_bytes) << " mbps\n" << "Incoming udp pps: " << current_attack.traffic_counters.udp.in_packets << " packets per second\n" << "Outgoing udp pps: " << current_attack.traffic_counters.udp.out_packets << " packets per second\n" << "Incoming icmp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.icmp.in_bytes) << " mbps\n" << "Outgoing icmp traffic: " << convert_speed_to_mbps(current_attack.traffic_counters.icmp.out_bytes) << " mbps\n" << "Incoming icmp pps: " << current_attack.traffic_counters.icmp.in_packets << " packets per second\n" << "Outgoing icmp pps: " << current_attack.traffic_counters.icmp.out_packets << " packets per second\n"; return attack_description.str(); } attack_type_t detect_attack_type(const attack_details_t& current_attack) { double threshold_value = 0.9; if (current_attack.attack_direction == INCOMING) { if (current_attack.traffic_counters.tcp_syn.in_packets > threshold_value * current_attack.traffic_counters.total.in_packets) { return ATTACK_SYN_FLOOD; } else if (current_attack.traffic_counters.icmp.in_packets > threshold_value * current_attack.traffic_counters.total.in_packets) { return ATTACK_ICMP_FLOOD; } else if (current_attack.traffic_counters.fragmented.in_packets > threshold_value * current_attack.traffic_counters.total.in_packets) { return ATTACK_IP_FRAGMENTATION_FLOOD; } else if (current_attack.traffic_counters.udp.in_packets > threshold_value * current_attack.traffic_counters.total.in_packets) { return ATTACK_UDP_FLOOD; } } else if (current_attack.attack_direction == OUTGOING) { if (current_attack.traffic_counters.tcp_syn.out_packets > threshold_value * current_attack.traffic_counters.total.out_packets) { return ATTACK_SYN_FLOOD; } else if (current_attack.traffic_counters.icmp.out_packets > threshold_value * current_attack.traffic_counters.total.out_packets) { return ATTACK_ICMP_FLOOD; } else if (current_attack.traffic_counters.fragmented.out_packets > threshold_value * current_attack.traffic_counters.total.out_packets) { return ATTACK_IP_FRAGMENTATION_FLOOD; } else if (current_attack.traffic_counters.udp.out_packets > threshold_value * current_attack.traffic_counters.total.out_packets) { return ATTACK_UDP_FLOOD; } } return ATTACK_UNKNOWN; } std::string get_printable_attack_name(attack_type_t attack) { if (attack == ATTACK_SYN_FLOOD) { return "syn_flood"; } else if (attack == ATTACK_ICMP_FLOOD) { return "icmp_flood"; } else if (attack == ATTACK_UDP_FLOOD) { return "udp_flood"; } else if (attack == ATTACK_IP_FRAGMENTATION_FLOOD) { return "ip_fragmentation"; } else if (attack == ATTACK_UNKNOWN) { return "unknown"; } else { return "unknown"; } } std::string serialize_network_load_to_text(subnet_counter_t& network_speed_meter, bool average) { std::stringstream buffer; std::string prefix = "Network"; if (average) { prefix = "Average network"; } buffer << prefix << " incoming traffic: " << convert_speed_to_mbps(network_speed_meter.total.in_bytes) << " mbps\n" << prefix << " outgoing traffic: " << convert_speed_to_mbps(network_speed_meter.total.out_bytes) << " mbps\n" << prefix << " incoming pps: " << network_speed_meter.total.in_packets << " packets per second\n" << prefix << " outgoing pps: " << network_speed_meter.total.out_packets << " packets per second\n"; return buffer.str(); } std::string dns_lookup(std::string domain_name) { try { boost::asio::io_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_ip_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_ip_address = root.getAgentIpAddress(); // Extract IPv6 addresses from packet if (packet.ip_protocol_version == 6) { if (root.hasSrcIpv6()) { ::capnp::Data::Reader reader_ipv6_data = root.getSrcIpv6(); if (reader_ipv6_data.size() == 16) { // Copy internal structure to C++ struct // TODO: move this code to something more high level, please memcpy((void*)&packet.src_ipv6, reader_ipv6_data.begin(), reader_ipv6_data.size()); } else { logger << log4cpp::Priority::ERROR << "broken size for IPv6 source address"; } } if (root.hasDstIpv6()) { ::capnp::Data::Reader reader_ipv6_data = root.getDstIpv6(); if (reader_ipv6_data.size() == 16) { // Copy internal structure to C++ struct // TODO: move this code to something more high level, please memcpy((void*)&packet.dst_ipv6, reader_ipv6_data.begin(), reader_ipv6_data.size()); } else { logger << log4cpp::Priority::ERROR << "broken size for IPv6 destination address"; } } // TODO: if we could not read src of dst IP addresses here we should drop this packet } packet.ttl = root.getTtl(); packet.source_port = root.getSourcePort(); packet.destination_port = root.getDestinationPort(); packet.length = root.getLength(); packet.number_of_packets = root.getNumberOfPackets(); packet.flags = root.getFlags(); packet.ip_fragmented = root.getIpFragmented(); packet.ts.tv_sec = root.getTsSec(); packet.ts.tv_usec = root.getTsMsec(); packet.captured_payload_length = root.getPacketPayloadLength(); packet.payload_full_length = root.getPacketPayloadFullLength(); packet.packet_direction = (direction_t)root.getPacketDirection(); packet.source = (source_t)root.getSource(); } catch (kj::Exception& e) { logger << log4cpp::Priority::WARN << "Exception happened during attempt to parse tera flow packet: " << e.getDescription().cStr(); return false; } catch (...) { logger << log4cpp::Priority::WARN << "Exception happened during attempt to parse tera flow packet"; return false; } return true; } 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; } upstream-fastnetmon/src/network_data_structures.hpp0000664000175000017500000012103415060514305021145 0ustar meme#pragma once #include #include #include #include #include #include #include "fast_endianless.hpp" #include "iana_ethertypes.hpp" #include "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)); } namespace network_data_stuctures { // 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(); } // TODO: it's not finished yet class __attribute__((__packed__)) mpls_label_t { public: uint32_t label : 20 = 0, qos : 3 = 0, bottom_of_stack : 1 = 0, ttl : 8 = 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(); } }; 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/rfc2784 class __attribute__((__packed__)) gre_header_t { // We must not access these fields directly as they may need conversion private: // TODO: we have no pcaps where these fields are no zeros and we did not test this case // For some reasons PVS thinks that we did not initialised all members. I think they're not that great with bitfields uint16_t checksum : 1 = 0, reserved : 12 = 0, version : 3 = 0; //-V730 uint16_t protocol_type = 0; public: uint16_t get_protocol_type_host_byte_order() const { return fast_ntoh(protocol_type); } uint16_t get_reserved() const { return reserved; } uint16_t get_checksum() const { return checksum; } uint16_t get_version() const { return version; } std::string print() const { std::stringstream buffer; buffer << "checksum: " << get_checksum() << " " << "reserved: " << get_reserved() << " " << "version: " << 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"); enum class parser_code_t { memory_violation, not_ipv4, success, broken_gre, no_ipv6_support, no_ipv6_options_support, unknown_ethertype, arp, too_many_nested_vlans, }; std::string parser_code_to_string(parser_code_t code); } // namespace network_data_stuctures upstream-fastnetmon/src/ban_list.hpp0000664000175000017500000001071015060514305015751 0ustar meme#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; }; upstream-fastnetmon/src/speed_counters.cpp0000664000175000017500000001675215060514305017207 0ustar meme#include "speed_counters.hpp" #include "fast_library.hpp" #include "iana_ip_protocols.hpp" extern time_t current_inaccurate_time; // This function increments all our accumulators according to data from packet void increment_incoming_counters(subnet_counter_t& current_element, const simple_packet_t& current_packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes) { // Update last update time current_element.last_update_time = current_inaccurate_time; // Main packet/bytes counter current_element.total.in_packets += sampled_number_of_packets; current_element.total.in_bytes += sampled_number_of_bytes; // Count fragmented IP packets if (current_packet.ip_fragmented) { current_element.fragmented.in_packets += sampled_number_of_packets; current_element.fragmented.in_bytes += sampled_number_of_bytes; } // Count dropped packets if (current_packet.forwarding_status == forwarding_status_t::dropped) { current_element.dropped.in_packets += sampled_number_of_packets; current_element.dropped.in_bytes += sampled_number_of_bytes; } // Count per protocol packets if (current_packet.protocol == IPPROTO_TCP) { current_element.tcp.in_packets += sampled_number_of_packets; current_element.tcp.in_bytes += sampled_number_of_bytes; if (extract_bit_value(current_packet.flags, TCP_SYN_FLAG_SHIFT)) { current_element.tcp_syn.in_packets += sampled_number_of_packets; current_element.tcp_syn.in_bytes += sampled_number_of_bytes; } } else if (current_packet.protocol == IPPROTO_UDP) { current_element.udp.in_packets += sampled_number_of_packets; current_element.udp.in_bytes += sampled_number_of_bytes; } else { // TBD } // ICMP uses different protocol numbers for IPv4 and IPv6 and we need handle it if (current_packet.ip_protocol_version == 4) { if (current_packet.protocol == IpProtocolNumberICMP) { current_element.icmp.in_packets += sampled_number_of_packets; current_element.icmp.in_bytes += sampled_number_of_bytes; } } else if (current_packet.ip_protocol_version == 6) { if (current_packet.protocol == IpProtocolNumberIPV6_ICMP) { current_element.icmp.in_packets += sampled_number_of_packets; current_element.icmp.in_bytes += sampled_number_of_bytes; } } } // Increment fields using data from specified packet void increment_outgoing_counters(subnet_counter_t& current_element, const simple_packet_t& current_packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes) { // Update last update time current_element.last_update_time = current_inaccurate_time; // Main packet/bytes counter current_element.total.out_packets += sampled_number_of_packets; current_element.total.out_bytes += sampled_number_of_bytes; // Fragmented IP packets if (current_packet.ip_fragmented) { current_element.fragmented.out_packets += sampled_number_of_packets; current_element.fragmented.out_bytes += sampled_number_of_bytes; } // Count dropped packets if (current_packet.forwarding_status == forwarding_status_t::dropped) { current_element.dropped.out_packets += sampled_number_of_packets; current_element.dropped.out_bytes += sampled_number_of_bytes; } if (current_packet.protocol == IPPROTO_TCP) { current_element.tcp.out_packets += sampled_number_of_packets; current_element.tcp.out_bytes += sampled_number_of_bytes; if (extract_bit_value(current_packet.flags, TCP_SYN_FLAG_SHIFT)) { current_element.tcp_syn.out_packets += sampled_number_of_packets; current_element.tcp_syn.out_bytes += sampled_number_of_bytes; } } else if (current_packet.protocol == IPPROTO_UDP) { current_element.udp.out_packets += sampled_number_of_packets; current_element.udp.out_bytes += sampled_number_of_bytes; } else { } // ICMP uses different protocol numbers for IPv4 and IPv6 and we need handle it if (current_packet.ip_protocol_version == 4) { if (current_packet.protocol == IpProtocolNumberICMP) { current_element.icmp.out_packets += sampled_number_of_packets; current_element.icmp.out_bytes += sampled_number_of_bytes; } } else if (current_packet.ip_protocol_version == 6) { if (current_packet.protocol == IpProtocolNumberIPV6_ICMP) { current_element.icmp.out_packets += sampled_number_of_packets; current_element.icmp.out_bytes += sampled_number_of_bytes; } } } // These build_* functions are called from our heavy computation path in recalculate_speed() // and you may have an idea that making them inline will help // We did this experiment and inlining clearly did speed calculation performance 1-2% worse // We calculate speed from packet counters here void build_speed_counters_from_packet_counters(subnet_counter_t& new_speed_element, const subnet_counter_t& data_counters, double speed_calc_period) { new_speed_element.total.calculate_speed(data_counters.total, speed_calc_period); new_speed_element.dropped.calculate_speed(data_counters.dropped, speed_calc_period); new_speed_element.fragmented.calculate_speed(data_counters.fragmented, speed_calc_period); new_speed_element.tcp_syn.calculate_speed(data_counters.tcp_syn, speed_calc_period); new_speed_element.tcp.calculate_speed(data_counters.tcp, speed_calc_period); new_speed_element.udp.calculate_speed(data_counters.udp, speed_calc_period); new_speed_element.icmp.calculate_speed(data_counters.icmp, speed_calc_period); } // We use this code to create smoothed speed of traffic from instant speed (per second) void build_average_speed_counters_from_speed_counters(subnet_counter_t& current_average_speed_element, const subnet_counter_t& new_speed_element, double exp_value) { current_average_speed_element.total.calulate_exponential_moving_average_speed(new_speed_element.total, exp_value); current_average_speed_element.dropped.calulate_exponential_moving_average_speed(new_speed_element.dropped, exp_value); current_average_speed_element.fragmented.calulate_exponential_moving_average_speed(new_speed_element.fragmented, exp_value); current_average_speed_element.tcp_syn.calulate_exponential_moving_average_speed(new_speed_element.tcp_syn, exp_value); current_average_speed_element.tcp.calulate_exponential_moving_average_speed(new_speed_element.tcp, exp_value); current_average_speed_element.udp.calulate_exponential_moving_average_speed(new_speed_element.udp, exp_value); current_average_speed_element.icmp.calulate_exponential_moving_average_speed(new_speed_element.icmp, exp_value); // We do calculate flow counters for all cases current_average_speed_element.out_flows = uint64_t(new_speed_element.out_flows + exp_value * ((double)current_average_speed_element.out_flows - (double)new_speed_element.out_flows)); current_average_speed_element.in_flows = uint64_t(new_speed_element.in_flows + exp_value * ((double)current_average_speed_element.in_flows - (double)new_speed_element.in_flows)); } upstream-fastnetmon/src/all_logcpp_libraries.hpp0000664000175000017500000000063615060514305020334 0ustar meme#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 upstream-fastnetmon/src/fastnetmon_configuration_scheme.hpp0000664000175000017500000000537615060514305022623 0ustar meme#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" }; }; upstream-fastnetmon/src/libsflow/0000755000175000017500000000000015060514305015265 5ustar memeupstream-fastnetmon/src/libsflow/libsflow.cpp0000664000175000017500000004407415060514305017625 0ustar meme#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(); } upstream-fastnetmon/src/libsflow/libsflow.hpp0000664000175000017500000011155215060514305017626 0ustar meme#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); upstream-fastnetmon/src/attack_details.hpp0000664000175000017500000000405515060514305017137 0ustar meme#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; upstream-fastnetmon/src/fast_library.hpp0000664000175000017500000002211115060514305016635 0ustar meme#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 upstream-fastnetmon/src/fastnetmon_pcap_format.hpp0000664000175000017500000001303015060514305020705 0ustar meme#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; }; upstream-fastnetmon/src/nlohmann/0000775000175000017500000000000015060514305015260 5ustar memeupstream-fastnetmon/src/nlohmann/json.hpp0000664000175000017500000304243415060514305016754 0ustar meme/* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ | | |__ | | | | | | version 3.10.5 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2013-2022 Niels Lohmann . 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. */ /****************************************************************************\ * 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 doc/README.md. * \****************************************************************************/ #ifndef INCLUDE_NLOHMANN_JSON_HPP_ #define INCLUDE_NLOHMANN_JSON_HPP_ #define NLOHMANN_JSON_VERSION_MAJOR 3 #define NLOHMANN_JSON_VERSION_MINOR 10 #define NLOHMANN_JSON_VERSION_PATCH 5 #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 // accumulate #include // string, stoi, to_string #include // declval, forward, move, pair, swap #include // vector // #include #include #include // #include #include // transform #include // array #include // forward_list #include // inserter, front_inserter, end #include // map #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 #include // exception #include // runtime_error #include // to_string #include // vector // #include #include // array #include // size_t #include // uint8_t #include // string namespace nlohmann { 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 */ inline bool operator<(const value_t lhs, const value_t rhs) noexcept { 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); return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; } } // namespace detail } // namespace nlohmann // #include #include // #include #include // declval, pair // #include /* Hedley - https://nemequ.github.io/hedley * Created by Evan Nemerson * * To the extent possible under law, the author(s) have dedicated all * copyright and related and neighboring rights to this software to * the public domain worldwide. This software is distributed without * any warranty. * * For details, see . * SPDX-License-Identifier: CC0-1.0 */ #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) */ // #include #include // #include namespace nlohmann { namespace detail { template struct make_void { using type = void; }; template using void_t = typename make_void::type; } // namespace detail } // namespace nlohmann // https://en.cppreference.com/w/cpp/experimental/is_detected namespace nlohmann { namespace detail { 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 } // namespace nlohmann // This file contains all internal macro definitions // You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them // 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_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_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (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 #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 < 1940 #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 // 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) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ 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) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ 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> #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); /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE @since version 3.9.0 */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ friend void from_json(const nlohmann::json& 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 @since version 3.9.0 */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } // inspired from https://stackoverflow.com/a/26745591 // allows to call 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_DIAGNOSTICS #define JSON_DIAGNOSTICS 0 #endif namespace nlohmann { 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 */ inline void replace_substring(std::string& s, const std::string& f, const std::string& t) { JSON_ASSERT(!f.empty()); for (auto pos = s.find(f); // find first occurrence of f pos != std::string::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. */ inline std::string escape(std::string s) { replace_substring(s, "~", "~0"); replace_substring(s, "/", "~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. */ static void unescape(std::string& s) { replace_substring(s, "~1", "/"); replace_substring(s, "~0", "~"); } } // namespace detail } // namespace nlohmann // #include #include // size_t namespace nlohmann { 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 } // namespace nlohmann // #include namespace nlohmann { namespace detail { //////////////// // exceptions // //////////////// /// @brief general exception of the @ref basic_json class /// @sa https://json.nlohmann.me/api/basic_json/exception/ class exception : public std::exception { public: /// returns the explanatory string const char* what() const noexcept override { return m.what(); } /// the id of the exception const int id; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes) protected: JSON_HEDLEY_NON_NULL(3) exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} // NOLINT(bugprone-throw-keyword-missing) static std::string name(const std::string& ename, int id_) { return "[json.exception." + ename + "." + std::to_string(id_) + "] "; } template static std::string diagnostics(const BasicJsonType& leaf_element) { #if JSON_DIAGNOSTICS std::vector tokens; for (const auto* current = &leaf_element; current->m_parent != nullptr; current = current->m_parent) { switch (current->m_parent->type()) { case value_t::array: { for (std::size_t i = 0; i < current->m_parent->m_value.array->size(); ++i) { if (¤t->m_parent->m_value.array->operator[](i) == current) { tokens.emplace_back(std::to_string(i)); break; } } break; } case value_t::object: { for (const auto& element : *current->m_parent->m_value.object) { if (&element.second == current) { tokens.emplace_back(element.first.c_str()); break; } } break; } case value_t::null: // LCOV_EXCL_LINE case value_t::string: // LCOV_EXCL_LINE case value_t::boolean: // LCOV_EXCL_LINE case value_t::number_integer: // LCOV_EXCL_LINE case value_t::number_unsigned: // LCOV_EXCL_LINE case value_t::number_float: // LCOV_EXCL_LINE case value_t::binary: // LCOV_EXCL_LINE case value_t::discarded: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE break; // LCOV_EXCL_LINE } } if (tokens.empty()) { return ""; } return "(" + std::accumulate(tokens.rbegin(), tokens.rend(), std::string{}, [](const std::string & a, const std::string & b) { return a + "/" + detail::escape(b); }) + ") "; #else static_cast(leaf_element); return ""; #endif } private: /// an exception object as storage for error messages std::runtime_error m; }; /// @brief exception indicating a parse error /// @sa https://json.nlohmann.me/api/basic_json/parse_error/ class parse_error : public exception { public: /*! @brief create a parse error exception @param[in] id_ the id of the exception @param[in] pos the position where the error occurred (or with chars_read_total=0 if the position cannot be determined) @param[in] what_arg the explanatory string @return parse_error object */ template static parse_error create(int id_, const position_t& pos, const std::string& what_arg, const BasicJsonType& context) { std::string w = exception::name("parse_error", id_) + "parse error" + position_string(pos) + ": " + exception::diagnostics(context) + what_arg; return {id_, pos.chars_read_total, w.c_str()}; } template static parse_error create(int id_, std::size_t byte_, const std::string& what_arg, const BasicJsonType& context) { std::string w = exception::name("parse_error", id_) + "parse error" + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + ": " + exception::diagnostics(context) + what_arg; return {id_, byte_, w.c_str()}; } /*! @brief byte index of the parse error The byte index of the last read character in the input file. @note For an input with n bytes, 1 is the index of the first character and n+1 is the index of the terminating null byte or the end of file. This also holds true when reading a byte vector (CBOR or MessagePack). */ const std::size_t byte; private: parse_error(int id_, std::size_t byte_, const char* what_arg) : exception(id_, what_arg), byte(byte_) {} static std::string position_string(const position_t& pos) { return " at line " + std::to_string(pos.lines_read + 1) + ", column " + std::to_string(pos.chars_read_current_line); } }; /// @brief exception indicating errors with iterators /// @sa https://json.nlohmann.me/api/basic_json/invalid_iterator/ class invalid_iterator : public exception { public: template static invalid_iterator create(int id_, const std::string& what_arg, const BasicJsonType& context) { std::string w = exception::name("invalid_iterator", id_) + exception::diagnostics(context) + what_arg; return {id_, w.c_str()}; } private: JSON_HEDLEY_NON_NULL(3) invalid_iterator(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /// @brief exception indicating executing a member function with a wrong type /// @sa https://json.nlohmann.me/api/basic_json/type_error/ class type_error : public exception { public: template static type_error create(int id_, const std::string& what_arg, const BasicJsonType& context) { std::string w = exception::name("type_error", id_) + exception::diagnostics(context) + what_arg; return {id_, w.c_str()}; } private: JSON_HEDLEY_NON_NULL(3) type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /// @brief exception indicating access out of the defined range /// @sa https://json.nlohmann.me/api/basic_json/out_of_range/ class out_of_range : public exception { public: template static out_of_range create(int id_, const std::string& what_arg, const BasicJsonType& context) { std::string w = exception::name("out_of_range", id_) + exception::diagnostics(context) + what_arg; return {id_, w.c_str()}; } private: JSON_HEDLEY_NON_NULL(3) out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /// @brief exception indicating other library errors /// @sa https://json.nlohmann.me/api/basic_json/other_error/ class other_error : public exception { public: template static other_error create(int id_, const std::string& what_arg, const BasicJsonType& context) { std::string w = exception::name("other_error", id_) + exception::diagnostics(context) + what_arg; return {id_, w.c_str()}; } private: JSON_HEDLEY_NON_NULL(3) other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; } // namespace detail } // namespace nlohmann // #include // #include #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 namespace nlohmann { 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 constexpr T value{}; }; template constexpr T static_const::value; // NOLINT(readability-redundant-declaration) } // namespace detail } // namespace nlohmann // #include namespace nlohmann { namespace detail { // dispatching helper struct template struct identity_tag {}; } // namespace detail } // namespace nlohmann // #include #include // numeric_limits #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval #include // tuple // #include // #include #include // random_access_iterator_tag // #include // #include namespace nlohmann { 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 } // namespace nlohmann // #include // #include namespace nlohmann { NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); } // namespace nlohmann // #include // #include namespace nlohmann { NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); } // namespace nlohmann // #include // #include // #include #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 /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann @since version 1.0.0 */ namespace nlohmann { /*! @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> 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; } // namespace nlohmann #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ namespace nlohmann { /*! @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 {}; ////////////////////// // 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; }; /////////////////// // is_ functions // /////////////////// // https://en.cppreference.com/w/cpp/types/conjunction template struct conjunction : std::true_type { }; template struct conjunction : B1 { }; template struct conjunction : std::conditional, B1>::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 { static constexpr auto value = is_constructible::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...> {}; // a naive helper to check if a type is an ordered_map (exploits the fact that // ordered_map inherits capacity() from std::vector) template struct is_ordered_map { using one = char; struct two { char x[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) }; template static one test( decltype(&C::capacity) ) ; template static two test(...); enum { value = sizeof(test(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) }; // to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) template < typename T, typename U, enable_if_t < !std::is_same::value, int > = 0 > T conditional_static_cast(U value) { return static_cast(value); } template::value, int> = 0> T conditional_static_cast(U value) { return value; } } // namespace detail } // namespace nlohmann // #include #if JSON_HAS_EXPERIMENTAL_FILESYSTEM #include namespace nlohmann::detail { namespace std_fs = std::experimental::filesystem; } // namespace nlohmann::detail #elif JSON_HAS_FILESYSTEM #include namespace nlohmann::detail { namespace std_fs = std::filesystem; } // namespace nlohmann::detail #endif namespace nlohmann { namespace detail { template void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()), j)); } n = nullptr; } // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& !std::is_same::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { case value_t::number_unsigned: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_integer: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_float: { val = static_cast(*j.template get_ptr()); break; } case value_t::null: case value_t::object: case value_t::array: case value_t::string: case value_t::boolean: case value_t::binary: case value_t::discarded: default: JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j)); } } template void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()), j)); } b = *j.template get_ptr(); } template void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j)); } s = *j.template get_ptr(); } template < typename BasicJsonType, typename ConstructibleStringType, enable_if_t < is_constructible_string_type::value&& !std::is_same::value, int > = 0 > void from_json(const BasicJsonType& j, ConstructibleStringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j)); } s = *j.template get_ptr(); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) { get_arithmetic_value(j, val); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) { get_arithmetic_value(j, val); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) { get_arithmetic_value(j, val); } template::value, int> = 0> void from_json(const BasicJsonType& j, EnumType& e) { typename std::underlying_type::type val; get_arithmetic_value(j, val); e = static_cast(val); } // forward_list doesn't have an insert method template::value, int> = 0> void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); } l.clear(); std::transform(j.rbegin(), j.rend(), std::front_inserter(l), [](const BasicJsonType & i) { return i.template get(); }); } // valarray doesn't have an insert method template::value, int> = 0> void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); } l.resize(j.size()); std::transform(j.begin(), j.end(), std::begin(l), [](const BasicJsonType & elem) { return elem.template get(); }); } template auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) -> decltype(j.template get(), void()) { for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); } } template void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); } template auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) -> decltype(j.template get(), void()) { for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); } } template::value, int> = 0> auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) -> decltype( arr.reserve(std::declval()), j.template get(), void()) { using std::end; ConstructibleArrayType ret; ret.reserve(j.size()); std::transform(j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType return i.template get(); }); arr = std::move(ret); } template::value, int> = 0> void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<0> /*unused*/) { using std::end; ConstructibleArrayType ret; std::transform( j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType return i.template get(); }); arr = std::move(ret); } template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < is_constructible_array_type::value&& !is_constructible_object_type::value&& !is_constructible_string_type::value&& !std::is_same::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) -> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), j.template get(), void()) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); } from_json_array_impl(j, arr, priority_tag<3> {}); } template < typename BasicJsonType, typename T, std::size_t... Idx > std::array from_json_inplace_array_impl(BasicJsonType&& j, identity_tag> /*unused*/, index_sequence /*unused*/) { return { { std::forward(j).at(Idx).template get()... } }; } template < typename BasicJsonType, typename T, std::size_t N > auto from_json(BasicJsonType&& j, identity_tag> tag) -> decltype(from_json_inplace_array_impl(std::forward(j), tag, make_index_sequence {})) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); } return from_json_inplace_array_impl(std::forward(j), tag, make_index_sequence {}); } template void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()), j)); } bin = *j.template get_ptr(); } template::value, int> = 0> void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()), j)); } ConstructibleObjectType ret; const auto* inner_object = j.template get_ptr(); using value_type = typename ConstructibleObjectType::value_type; std::transform( inner_object->begin(), inner_object->end(), std::inserter(ret, ret.begin()), [](typename BasicJsonType::object_t::value_type const & p) { return value_type(p.first, p.second.template get()); }); obj = std::move(ret); } // overload for arithmetic types, not chosen for basic_json template arguments // (BooleanType, etc..); note: Is it really necessary to provide explicit // overloads for boolean_t etc. in case of a custom BooleanType which is not // an arithmetic type? template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& !std::is_same::value&& !std::is_same::value&& !std::is_same::value&& !std::is_same::value, int > = 0 > void from_json(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { case value_t::number_unsigned: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_integer: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_float: { val = static_cast(*j.template get_ptr()); break; } case value_t::boolean: { val = static_cast(*j.template get_ptr()); break; } case value_t::null: case value_t::object: case value_t::array: case value_t::string: case value_t::binary: case value_t::discarded: default: JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j)); } } template std::tuple from_json_tuple_impl_base(BasicJsonType&& j, index_sequence /*unused*/) { return std::make_tuple(std::forward(j).at(Idx).template get()...); } template < typename BasicJsonType, class A1, class A2 > std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<0> /*unused*/) { return {std::forward(j).at(0).template get(), std::forward(j).at(1).template get()}; } template void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) { p = from_json_tuple_impl(std::forward(j), identity_tag> {}, priority_tag<0> {}); } template std::tuple from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) { return from_json_tuple_impl_base(std::forward(j), index_sequence_for {}); } template void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) { t = from_json_tuple_impl_base(std::forward(j), index_sequence_for {}); } template auto from_json(BasicJsonType&& j, TupleRelated&& t) -> decltype(from_json_tuple_impl(std::forward(j), std::forward(t), priority_tag<3> {})) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); } return from_json_tuple_impl(std::forward(j), std::forward(t), priority_tag<3> {}); } template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()), j)); } m.emplace(p.at(0).template get(), p.at(1).template get()); } } template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::unordered_map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()), j)); } m.emplace(p.at(0).template get(), p.at(1).template get()); } } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM template void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j)); } p = *j.template get_ptr(); } #endif struct from_json_fn { template auto operator()(const BasicJsonType& j, T&& val) const noexcept(noexcept(from_json(j, std::forward(val)))) -> decltype(from_json(j, std::forward(val))) { return from_json(j, std::forward(val)); } }; } // namespace detail /// namespace to hold default `from_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { constexpr const auto& from_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) } // namespace } // namespace nlohmann // #include #include // copy #include // begin, end #include // string #include // tuple, get #include // is_same, is_constructible, is_floating_point, is_enum, underlying_type #include // move, forward, declval, pair #include // valarray #include // vector // #include // #include #include // size_t #include // input_iterator_tag #include // string, to_string #include // tuple_size, get, tuple_element #include // move // #include // #include namespace nlohmann { namespace detail { template void int_to_string( string_type& target, std::size_t value ) { // For ADL using std::to_string; target = to_string(value); } template class iteration_proxy_value { public: using difference_type = std::ptrdiff_t; using value_type = iteration_proxy_value; using pointer = value_type * ; using reference = value_type & ; using iterator_category = std::input_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: /// the iterator IteratorType anchor; /// an index for arrays (used to create key names) std::size_t array_index = 0; /// last stringified array index mutable std::size_t array_index_last = 0; /// a string representation of the array index mutable string_type array_index_str = "0"; /// an empty string (to return a reference for primitive values) const string_type empty_str{}; public: explicit iteration_proxy_value(IteratorType it) noexcept : anchor(std::move(it)) {} /// dereference operator (needed for range-based for) iteration_proxy_value& operator*() { return *this; } /// increment operator (needed for range-based for) iteration_proxy_value& operator++() { ++anchor; ++array_index; return *this; } /// equality operator (needed for InputIterator) bool operator==(const iteration_proxy_value& o) const { return anchor == o.anchor; } /// inequality operator (needed for range-based for) bool operator!=(const iteration_proxy_value& o) const { return anchor != o.anchor; } /// return key of the iterator const string_type& key() const { JSON_ASSERT(anchor.m_object != nullptr); switch (anchor.m_object->type()) { // use integer array index as key case value_t::array: { if (array_index != array_index_last) { int_to_string( array_index_str, array_index ); array_index_last = array_index; } return array_index_str; } // use key from the object case value_t::object: return anchor.key(); // use an empty key for all primitive types case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: return empty_str; } } /// return value of the iterator typename IteratorType::reference value() const { return anchor.value(); } }; /// proxy class for the items() function template class iteration_proxy { private: /// the container to iterate typename IteratorType::reference container; public: /// construct iteration proxy from a container explicit iteration_proxy(typename IteratorType::reference cont) noexcept : container(cont) {} /// return iterator begin (needed for range-based for) iteration_proxy_value begin() noexcept { return iteration_proxy_value(container.begin()); } /// return iterator end (needed for range-based for) iteration_proxy_value end() noexcept { return iteration_proxy_value(container.end()); } }; // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 template = 0> auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.key()) { return i.key(); } // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 template = 0> auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.value()) { return i.value(); } } // namespace detail } // namespace nlohmann // The Addition to the STD Namespace is required to add // Structured Bindings Support to the iteration_proxy_value class // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 namespace std { #if defined(__clang__) // Fix: https://github.com/nlohmann/json/issues/1401 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-tags" #endif template class tuple_size<::nlohmann::detail::iteration_proxy_value> : public std::integral_constant {}; template class tuple_element> { public: using type = decltype( get(std::declval < ::nlohmann::detail::iteration_proxy_value> ())); }; #if defined(__clang__) #pragma clang diagnostic pop #endif } // namespace std // #include // #include // #include #if JSON_HAS_EXPERIMENTAL_FILESYSTEM #include namespace nlohmann::detail { namespace std_fs = std::experimental::filesystem; } // namespace nlohmann::detail #elif JSON_HAS_FILESYSTEM #include namespace nlohmann::detail { namespace std_fs = std::filesystem; } // namespace nlohmann::detail #endif namespace nlohmann { namespace detail { ////////////////// // constructors // ////////////////// /* * Note all external_constructor<>::construct functions need to call * j.m_value.destroy(j.m_type) to avoid a memory leak in case j contains an * allocated value (e.g., a string). See bug issue * https://github.com/nlohmann/json/issues/2865 for more information. */ template struct external_constructor; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept { j.m_value.destroy(j.m_type); j.m_type = value_t::boolean; j.m_value = b; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) { j.m_value.destroy(j.m_type); j.m_type = value_t::string; j.m_value = s; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) { j.m_value.destroy(j.m_type); j.m_type = value_t::string; j.m_value = std::move(s); j.assert_invariant(); } template < typename BasicJsonType, typename CompatibleStringType, enable_if_t < !std::is_same::value, int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { j.m_value.destroy(j.m_type); j.m_type = value_t::string; j.m_value.string = j.template create(str); j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) { j.m_value.destroy(j.m_type); j.m_type = value_t::binary; j.m_value = typename BasicJsonType::binary_t(b); j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) { j.m_value.destroy(j.m_type); j.m_type = value_t::binary; j.m_value = typename BasicJsonType::binary_t(std::move(b)); j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept { j.m_value.destroy(j.m_type); j.m_type = value_t::number_float; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept { j.m_value.destroy(j.m_type); j.m_type = value_t::number_unsigned; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept { j.m_value.destroy(j.m_type); j.m_type = value_t::number_integer; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) { j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = arr; j.set_parents(); j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = std::move(arr); j.set_parents(); j.assert_invariant(); } template < typename BasicJsonType, typename CompatibleArrayType, enable_if_t < !std::is_same::value, int > = 0 > static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { using std::begin; using std::end; j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value.array = j.template create(begin(arr), end(arr)); j.set_parents(); j.assert_invariant(); } template static void construct(BasicJsonType& j, const std::vector& arr) { j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->reserve(arr.size()); for (const bool x : arr) { j.m_value.array->push_back(x); j.set_parent(j.m_value.array->back()); } j.assert_invariant(); } template::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& arr) { j.m_value.destroy(j.m_type); j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->resize(arr.size()); if (arr.size() > 0) { std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); } j.set_parents(); j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { j.m_value.destroy(j.m_type); j.m_type = value_t::object; j.m_value = obj; j.set_parents(); j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { j.m_value.destroy(j.m_type); j.m_type = value_t::object; j.m_value = std::move(obj); j.set_parents(); j.assert_invariant(); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < !std::is_same::value, int > = 0 > static void construct(BasicJsonType& j, const CompatibleObjectType& obj) { using std::begin; using std::end; j.m_value.destroy(j.m_type); j.m_type = value_t::object; j.m_value.object = j.template create(begin(obj), end(obj)); j.set_parents(); j.assert_invariant(); } }; ///////////// // to_json // ///////////// template::value, int> = 0> void to_json(BasicJsonType& j, T b) noexcept { external_constructor::construct(j, b); } template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } template void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) { external_constructor::construct(j, std::move(s)); } template::value, int> = 0> void to_json(BasicJsonType& j, FloatType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } template void to_json(BasicJsonType& j, const std::vector& e) { external_constructor::construct(j, e); } template < typename BasicJsonType, typename CompatibleArrayType, enable_if_t < is_compatible_array_type::value&& !is_compatible_object_type::value&& !is_compatible_string_type::value&& !std::is_same::value&& !is_basic_json::value, int > = 0 > void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } template void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { external_constructor::construct(j, bin); } template::value, int> = 0> void to_json(BasicJsonType& j, const std::valarray& arr) { external_constructor::construct(j, std::move(arr)); } template void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { external_constructor::construct(j, std::move(arr)); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { external_constructor::construct(j, obj); } template void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { external_constructor::construct(j, std::move(obj)); } template < typename BasicJsonType, typename T, std::size_t N, enable_if_t < !std::is_constructible::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) int > = 0 > void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { external_constructor::construct(j, arr); } template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > void to_json(BasicJsonType& j, const std::pair& p) { j = { p.first, p.second }; } // for https://github.com/nlohmann/json/pull/1134 template>::value, int> = 0> void to_json(BasicJsonType& j, const T& b) { j = { {b.key(), b.value()} }; } template void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) { j = { std::get(t)... }; } template::value, int > = 0> void to_json(BasicJsonType& j, const T& t) { to_json_tuple_impl(j, t, make_index_sequence::value> {}); } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM template void to_json(BasicJsonType& j, const std_fs::path& p) { j = p.string(); } #endif struct to_json_fn { template auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward(val)))) -> decltype(to_json(j, std::forward(val)), void()) { return to_json(j, std::forward(val)); } }; } // namespace detail /// namespace to hold default `to_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { constexpr const auto& to_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) } // namespace } // namespace nlohmann // #include // #include namespace nlohmann { /// @sa https://json.nlohmann.me/api/adl_serializer/ template struct adl_serializer { /// @brief convert a JSON value to any value type /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/ template static auto from_json(BasicJsonType && j, TargetType& val) noexcept( noexcept(::nlohmann::from_json(std::forward(j), val))) -> decltype(::nlohmann::from_json(std::forward(j), val), void()) { ::nlohmann::from_json(std::forward(j), val); } /// @brief convert a JSON value to any value type /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/ template static auto from_json(BasicJsonType && j) noexcept( noexcept(::nlohmann::from_json(std::forward(j), detail::identity_tag {}))) -> decltype(::nlohmann::from_json(std::forward(j), detail::identity_tag {})) { return ::nlohmann::from_json(std::forward(j), detail::identity_tag {}); } /// @brief convert any value type to a JSON value /// @sa https://json.nlohmann.me/api/adl_serializer/to_json/ template static auto to_json(BasicJsonType& j, TargetType && val) noexcept( noexcept(::nlohmann::to_json(j, std::forward(val)))) -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) { ::nlohmann::to_json(j, std::forward(val)); } }; } // namespace nlohmann // #include #include // uint8_t, uint64_t #include // tie #include // move namespace nlohmann { /// @brief an internal type for a backed binary type /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/ template class byte_container_with_subtype : public BinaryType { public: using container_type = BinaryType; using subtype_type = std::uint64_t; /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ byte_container_with_subtype() noexcept(noexcept(container_type())) : container_type() {} /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b))) : container_type(b) {} /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b)))) : container_type(std::move(b)) {} /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ byte_container_with_subtype(const container_type& b, subtype_type subtype_) noexcept(noexcept(container_type(b))) : container_type(b) , m_subtype(subtype_) , m_has_subtype(true) {} /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ byte_container_with_subtype(container_type&& b, subtype_type subtype_) noexcept(noexcept(container_type(std::move(b)))) : container_type(std::move(b)) , m_subtype(subtype_) , m_has_subtype(true) {} bool operator==(const byte_container_with_subtype& rhs) const { return std::tie(static_cast(*this), m_subtype, m_has_subtype) == std::tie(static_cast(rhs), rhs.m_subtype, rhs.m_has_subtype); } bool operator!=(const byte_container_with_subtype& rhs) const { return !(rhs == *this); } /// @brief sets the binary subtype /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/set_subtype/ void set_subtype(subtype_type subtype_) noexcept { m_subtype = subtype_; m_has_subtype = true; } /// @brief return the binary subtype /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/subtype/ constexpr subtype_type subtype() const noexcept { return m_has_subtype ? m_subtype : static_cast(-1); } /// @brief return whether the value has a subtype /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/has_subtype/ constexpr bool has_subtype() const noexcept { return m_has_subtype; } /// @brief clears the binary subtype /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/clear_subtype/ void clear_subtype() noexcept { m_subtype = 0; m_has_subtype = false; } private: subtype_type m_subtype = 0; bool m_has_subtype = false; }; } // namespace nlohmann // #include // #include // #include // #include #include // uint8_t #include // size_t #include // hash // #include // #include namespace nlohmann { namespace detail { // boost::hash_combine inline std::size_t combine(std::size_t seed, std::size_t h) noexcept { seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); return seed; } /*! @brief hash a JSON value The hash function tries to rely on std::hash where possible. Furthermore, the type of the JSON value is taken into account to have different hash values for null, 0, 0U, and false, etc. @tparam BasicJsonType basic_json specialization @param j JSON value to hash @return hash value of j */ template std::size_t hash(const BasicJsonType& j) { using string_t = typename BasicJsonType::string_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; const auto type = static_cast(j.type()); switch (j.type()) { case BasicJsonType::value_t::null: case BasicJsonType::value_t::discarded: { return combine(type, 0); } case BasicJsonType::value_t::object: { auto seed = combine(type, j.size()); for (const auto& element : j.items()) { const auto h = std::hash {}(element.key()); seed = combine(seed, h); seed = combine(seed, hash(element.value())); } return seed; } case BasicJsonType::value_t::array: { auto seed = combine(type, j.size()); for (const auto& element : j) { seed = combine(seed, hash(element)); } return seed; } case BasicJsonType::value_t::string: { const auto h = std::hash {}(j.template get_ref()); return combine(type, h); } case BasicJsonType::value_t::boolean: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case BasicJsonType::value_t::number_integer: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case BasicJsonType::value_t::number_unsigned: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case BasicJsonType::value_t::number_float: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case BasicJsonType::value_t::binary: { auto seed = combine(type, j.get_binary().size()); const auto h = std::hash {}(j.get_binary().has_subtype()); seed = combine(seed, h); seed = combine(seed, static_cast(j.get_binary().subtype())); for (const auto byte : j.get_binary()) { seed = combine(seed, std::hash {}(byte)); } return seed; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE return 0; // LCOV_EXCL_LINE } } } // namespace detail } // namespace nlohmann // #include #include // generate_n #include // array #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t #include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits #include // char_traits, string #include // make_pair, move #include // vector // #include // #include #include // array #include // size_t #include // strlen #include // begin, end, iterator_traits, random_access_iterator_tag, distance, next #include // shared_ptr, make_shared, addressof #include // accumulate #include // string, char_traits #include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer #include // pair, declval #ifndef JSON_NO_IO #include // FILE * #include // istream #endif // JSON_NO_IO // #include // #include namespace nlohmann { namespace detail { /// the supported input formats enum class input_format_t { json, cbor, msgpack, ubjson, bson }; //////////////////// // input adapters // //////////////////// #ifndef JSON_NO_IO /*! Input adapter for stdio file access. This adapter read only 1 byte and do not use any buffer. This adapter is a very low level adapter. */ class file_input_adapter { public: using char_type = char; JSON_HEDLEY_NON_NULL(2) explicit file_input_adapter(std::FILE* f) noexcept : m_file(f) {} // make class move-only file_input_adapter(const file_input_adapter&) = delete; file_input_adapter(file_input_adapter&&) noexcept = default; file_input_adapter& operator=(const file_input_adapter&) = delete; file_input_adapter& operator=(file_input_adapter&&) = delete; ~file_input_adapter() = default; std::char_traits::int_type get_character() noexcept { return std::fgetc(m_file); } private: /// the file pointer to read from std::FILE* m_file; }; /*! Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at beginning of input. Does not support changing the underlying std::streambuf in mid-input. Maintains underlying std::istream and std::streambuf to support subsequent use of standard std::istream operations to process any input characters following those used in parsing the JSON input. Clears the std::istream flags; any input errors (e.g., EOF) will be detected by the first subsequent call for input from the std::istream. */ class input_stream_adapter { public: using char_type = char; ~input_stream_adapter() { // clear stream flags; we use underlying streambuf I/O, do not // maintain ifstream flags, except eof if (is != nullptr) { is->clear(is->rdstate() & std::ios::eofbit); } } explicit input_stream_adapter(std::istream& i) : is(&i), sb(i.rdbuf()) {} // delete because of pointer members input_stream_adapter(const input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&&) = delete; input_stream_adapter(input_stream_adapter&& rhs) noexcept : is(rhs.is), sb(rhs.sb) { rhs.is = nullptr; rhs.sb = nullptr; } // std::istream/std::streambuf use std::char_traits::to_int_type, to // ensure that std::char_traits::eof() and the character 0xFF do not // end up as the same value, e.g. 0xFFFFFFFF. std::char_traits::int_type get_character() { auto res = sb->sbumpc(); // set eof manually, as we don't use the istream interface. if (JSON_HEDLEY_UNLIKELY(res == std::char_traits::eof())) { is->clear(is->rdstate() | std::ios::eofbit); } return res; } private: /// the associated input stream std::istream* is = nullptr; std::streambuf* sb = nullptr; }; #endif // JSON_NO_IO // General-purpose iterator-based adapter. It might not be as fast as // theoretically possible for some containers, but it is extremely versatile. template class iterator_input_adapter { public: using char_type = typename std::iterator_traits::value_type; iterator_input_adapter(IteratorType first, IteratorType last) : current(std::move(first)), end(std::move(last)) {} typename std::char_traits::int_type get_character() { if (JSON_HEDLEY_LIKELY(current != end)) { auto result = std::char_traits::to_int_type(*current); std::advance(current, 1); return result; } return std::char_traits::eof(); } private: IteratorType current; IteratorType end; template friend struct wide_string_input_helper; bool empty() const { return current == end; } }; template struct wide_string_input_helper; template struct wide_string_input_helper { // UTF-32 static void fill_buffer(BaseInputAdapter& input, std::array::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled) { utf8_bytes_index = 0; if (JSON_HEDLEY_UNLIKELY(input.empty())) { utf8_bytes[0] = std::char_traits::eof(); utf8_bytes_filled = 1; } else { // get the current character const auto wc = input.get_character(); // UTF-32 to UTF-8 encoding if (wc < 0x80) { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } else if (wc <= 0x7FF) { utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u) & 0x1Fu)); utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 2; } else if (wc <= 0xFFFF) { utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u) & 0x0Fu)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 3; } else if (wc <= 0x10FFFF) { utf8_bytes[0] = static_cast::int_type>(0xF0u | ((static_cast(wc) >> 18u) & 0x07u)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 12u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); utf8_bytes[3] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 4; } else { // unknown character utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } } } }; template struct wide_string_input_helper { // UTF-16 static void fill_buffer(BaseInputAdapter& input, std::array::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled) { utf8_bytes_index = 0; if (JSON_HEDLEY_UNLIKELY(input.empty())) { utf8_bytes[0] = std::char_traits::eof(); utf8_bytes_filled = 1; } else { // get the current character const auto wc = input.get_character(); // UTF-16 to UTF-8 encoding if (wc < 0x80) { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } else if (wc <= 0x7FF) { utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u))); utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 2; } else if (0xD800 > wc || wc >= 0xE000) { utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u))); utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 3; } else { if (JSON_HEDLEY_UNLIKELY(!input.empty())) { const auto wc2 = static_cast(input.get_character()); const auto charcode = 0x10000u + (((static_cast(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); utf8_bytes[0] = static_cast::int_type>(0xF0u | (charcode >> 18u)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); utf8_bytes[3] = static_cast::int_type>(0x80u | (charcode & 0x3Fu)); utf8_bytes_filled = 4; } else { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } } } } }; // Wraps another input apdater to convert wide character types into individual bytes. template class wide_string_input_adapter { public: using char_type = char; wide_string_input_adapter(BaseInputAdapter base) : base_adapter(base) {} typename std::char_traits::int_type get_character() noexcept { // check if buffer needs to be filled if (utf8_bytes_index == utf8_bytes_filled) { fill_buffer(); JSON_ASSERT(utf8_bytes_filled > 0); JSON_ASSERT(utf8_bytes_index == 0); } // use buffer JSON_ASSERT(utf8_bytes_filled > 0); JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled); return utf8_bytes[utf8_bytes_index++]; } private: BaseInputAdapter base_adapter; template void fill_buffer() { wide_string_input_helper::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled); } /// a buffer for UTF-8 bytes std::array::int_type, 4> utf8_bytes = {{0, 0, 0, 0}}; /// index to the utf8_codes array for the next valid byte std::size_t utf8_bytes_index = 0; /// number of valid bytes in the utf8_codes array std::size_t utf8_bytes_filled = 0; }; template struct iterator_input_adapter_factory { using iterator_type = IteratorType; using char_type = typename std::iterator_traits::value_type; using adapter_type = iterator_input_adapter; static adapter_type create(IteratorType first, IteratorType last) { return adapter_type(std::move(first), std::move(last)); } }; template struct is_iterator_of_multibyte { using value_type = typename std::iterator_traits::value_type; enum { value = sizeof(value_type) > 1 }; }; template struct iterator_input_adapter_factory::value>> { using iterator_type = IteratorType; using char_type = typename std::iterator_traits::value_type; using base_adapter_type = iterator_input_adapter; using adapter_type = wide_string_input_adapter; static adapter_type create(IteratorType first, IteratorType last) { return adapter_type(base_adapter_type(std::move(first), std::move(last))); } }; // General purpose iterator-based input template typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) { using factory_type = iterator_input_adapter_factory; return factory_type::create(first, last); } // Convenience shorthand from container to iterator // Enables ADL on begin(container) and end(container) // Encloses the using declarations in namespace for not to leak them to outside scope namespace container_input_adapter_factory_impl { using std::begin; using std::end; template struct container_input_adapter_factory {}; template struct container_input_adapter_factory< ContainerType, void_t()), end(std::declval()))>> { using adapter_type = decltype(input_adapter(begin(std::declval()), end(std::declval()))); static adapter_type create(const ContainerType& container) { return input_adapter(begin(container), end(container)); } }; } // namespace container_input_adapter_factory_impl template typename container_input_adapter_factory_impl::container_input_adapter_factory::adapter_type input_adapter(const ContainerType& container) { return container_input_adapter_factory_impl::container_input_adapter_factory::create(container); } #ifndef JSON_NO_IO // Special cases with fast paths inline file_input_adapter input_adapter(std::FILE* file) { return file_input_adapter(file); } inline input_stream_adapter input_adapter(std::istream& stream) { return input_stream_adapter(stream); } inline input_stream_adapter input_adapter(std::istream&& stream) { return input_stream_adapter(stream); } #endif // JSON_NO_IO using contiguous_bytes_input_adapter = decltype(input_adapter(std::declval(), std::declval())); // Null-delimited strings, and the like. template < typename CharT, typename std::enable_if < std::is_pointer::value&& !std::is_array::value&& std::is_integral::type>::value&& sizeof(typename std::remove_pointer::type) == 1, int >::type = 0 > contiguous_bytes_input_adapter input_adapter(CharT b) { auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); return input_adapter(ptr, ptr + length); } template auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { return input_adapter(array, array + N); } // This class only handles inputs of input_buffer_adapter type. // It's required so that expressions like {ptr, len} can be implicitly cast // to the correct adapter. class span_input_adapter { public: template < typename CharT, typename std::enable_if < std::is_pointer::value&& std::is_integral::type>::value&& sizeof(typename std::remove_pointer::type) == 1, int >::type = 0 > span_input_adapter(CharT b, std::size_t l) : ia(reinterpret_cast(b), reinterpret_cast(b) + l) {} template::iterator_category, std::random_access_iterator_tag>::value, int>::type = 0> span_input_adapter(IteratorType first, IteratorType last) : ia(input_adapter(first, last)) {} contiguous_bytes_input_adapter&& get() { return std::move(ia); // NOLINT(hicpp-move-const-arg,performance-move-const-arg) } private: contiguous_bytes_input_adapter ia; }; } // namespace detail } // namespace nlohmann // #include #include #include // string #include // move #include // vector // #include // #include namespace nlohmann { /*! @brief SAX interface This class describes the SAX interface used by @ref nlohmann::json::sax_parse. Each function is called in different situations while the input is parsed. The boolean return value informs the parser whether to continue processing the input. */ template struct json_sax { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; /*! @brief a null value was read @return whether parsing should proceed */ virtual bool null() = 0; /*! @brief a boolean value was read @param[in] val boolean value @return whether parsing should proceed */ virtual bool boolean(bool val) = 0; /*! @brief an integer number was read @param[in] val integer value @return whether parsing should proceed */ virtual bool number_integer(number_integer_t val) = 0; /*! @brief an unsigned integer number was read @param[in] val unsigned integer value @return whether parsing should proceed */ virtual bool number_unsigned(number_unsigned_t val) = 0; /*! @brief a floating-point number was read @param[in] val floating-point value @param[in] s raw token value @return whether parsing should proceed */ virtual bool number_float(number_float_t val, const string_t& s) = 0; /*! @brief a string value was read @param[in] val string value @return whether parsing should proceed @note It is safe to move the passed string value. */ virtual bool string(string_t& val) = 0; /*! @brief a binary value was read @param[in] val binary value @return whether parsing should proceed @note It is safe to move the passed binary value. */ virtual bool binary(binary_t& val) = 0; /*! @brief the beginning of an object was read @param[in] elements number of object elements or -1 if unknown @return whether parsing should proceed @note binary formats may report the number of elements */ virtual bool start_object(std::size_t elements) = 0; /*! @brief an object key was read @param[in] val object key @return whether parsing should proceed @note It is safe to move the passed string. */ virtual bool key(string_t& val) = 0; /*! @brief the end of an object was read @return whether parsing should proceed */ virtual bool end_object() = 0; /*! @brief the beginning of an array was read @param[in] elements number of array elements or -1 if unknown @return whether parsing should proceed @note binary formats may report the number of elements */ virtual bool start_array(std::size_t elements) = 0; /*! @brief the end of an array was read @return whether parsing should proceed */ virtual bool end_array() = 0; /*! @brief a parse error occurred @param[in] position the position in the input where the error occurs @param[in] last_token the last read token @param[in] ex an exception object describing the error @return whether parsing should proceed (must return false) */ virtual bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex) = 0; json_sax() = default; json_sax(const json_sax&) = default; json_sax(json_sax&&) noexcept = default; json_sax& operator=(const json_sax&) = default; json_sax& operator=(json_sax&&) noexcept = default; virtual ~json_sax() = default; }; namespace detail { /*! @brief SAX implementation to create a JSON value from SAX events This class implements the @ref json_sax interface and processes the SAX events to create a JSON value which makes it basically a DOM parser. The structure or hierarchy of the JSON value is managed by the stack `ref_stack` which contains a pointer to the respective array or object for each recursion depth. After successful parsing, the value that is passed by reference to the constructor contains the parsed value. @tparam BasicJsonType the JSON type */ template class json_sax_dom_parser { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; /*! @param[in,out] r reference to a JSON value that is manipulated while parsing @param[in] allow_exceptions_ whether parse errors yield exceptions */ explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) : root(r), allow_exceptions(allow_exceptions_) {} // make class move-only json_sax_dom_parser(const json_sax_dom_parser&) = delete; json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) ~json_sax_dom_parser() = default; bool null() { handle_value(nullptr); return true; } bool boolean(bool val) { handle_value(val); return true; } bool number_integer(number_integer_t val) { handle_value(val); return true; } bool number_unsigned(number_unsigned_t val) { handle_value(val); return true; } bool number_float(number_float_t val, const string_t& /*unused*/) { handle_value(val); return true; } bool string(string_t& val) { handle_value(val); return true; } bool binary(binary_t& val) { handle_value(std::move(val)); return true; } bool start_object(std::size_t len) { ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len), *ref_stack.back())); } return true; } bool key(string_t& val) { // add null at given key and store the reference for later object_element = &(ref_stack.back()->m_value.object->operator[](val)); return true; } bool end_object() { ref_stack.back()->set_parents(); ref_stack.pop_back(); return true; } bool start_array(std::size_t len) { ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len), *ref_stack.back())); } return true; } bool end_array() { ref_stack.back()->set_parents(); ref_stack.pop_back(); return true; } template bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex) { errored = true; static_cast(ex); if (allow_exceptions) { JSON_THROW(ex); } return false; } constexpr bool is_errored() const { return errored; } private: /*! @invariant If the ref stack is empty, then the passed value will be the new root. @invariant If the ref stack contains a value, then it is an array or an object to which we can add elements */ template JSON_HEDLEY_RETURNS_NON_NULL BasicJsonType* handle_value(Value&& v) { if (ref_stack.empty()) { root = BasicJsonType(std::forward(v)); return &root; } JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); if (ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->emplace_back(std::forward(v)); return &(ref_stack.back()->m_value.array->back()); } JSON_ASSERT(ref_stack.back()->is_object()); JSON_ASSERT(object_element); *object_element = BasicJsonType(std::forward(v)); return object_element; } /// the parsed JSON value BasicJsonType& root; /// stack to model hierarchy of values std::vector ref_stack {}; /// helper to hold the reference for the next object element BasicJsonType* object_element = nullptr; /// whether a syntax error occurred bool errored = false; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; template class json_sax_dom_callback_parser { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using parser_callback_t = typename BasicJsonType::parser_callback_t; using parse_event_t = typename BasicJsonType::parse_event_t; json_sax_dom_callback_parser(BasicJsonType& r, const parser_callback_t cb, const bool allow_exceptions_ = true) : root(r), callback(cb), allow_exceptions(allow_exceptions_) { keep_stack.push_back(true); } // make class move-only json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) ~json_sax_dom_callback_parser() = default; bool null() { handle_value(nullptr); return true; } bool boolean(bool val) { handle_value(val); return true; } bool number_integer(number_integer_t val) { handle_value(val); return true; } bool number_unsigned(number_unsigned_t val) { handle_value(val); return true; } bool number_float(number_float_t val, const string_t& /*unused*/) { handle_value(val); return true; } bool string(string_t& val) { handle_value(val); return true; } bool binary(binary_t& val) { handle_value(std::move(val)); return true; } bool start_object(std::size_t len) { // check callback for object start const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); keep_stack.push_back(keep); auto val = handle_value(BasicJsonType::value_t::object, true); ref_stack.push_back(val.second); // check object limit if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len), *ref_stack.back())); } return true; } bool key(string_t& val) { BasicJsonType k = BasicJsonType(val); // check callback for key const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); key_keep_stack.push_back(keep); // add discarded value at given key and store the reference for later if (keep && ref_stack.back()) { object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded); } return true; } bool end_object() { if (ref_stack.back()) { if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) { // discard object *ref_stack.back() = discarded; } else { ref_stack.back()->set_parents(); } } JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!keep_stack.empty()); ref_stack.pop_back(); keep_stack.pop_back(); if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) { // remove discarded value for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) { if (it->is_discarded()) { ref_stack.back()->erase(it); break; } } } return true; } bool start_array(std::size_t len) { const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); keep_stack.push_back(keep); auto val = handle_value(BasicJsonType::value_t::array, true); ref_stack.push_back(val.second); // check array limit if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len), *ref_stack.back())); } return true; } bool end_array() { bool keep = true; if (ref_stack.back()) { keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); if (keep) { ref_stack.back()->set_parents(); } else { // discard array *ref_stack.back() = discarded; } } JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!keep_stack.empty()); ref_stack.pop_back(); keep_stack.pop_back(); // remove discarded value if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->pop_back(); } return true; } template bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex) { errored = true; static_cast(ex); if (allow_exceptions) { JSON_THROW(ex); } return false; } constexpr bool is_errored() const { return errored; } private: /*! @param[in] v value to add to the JSON value we build during parsing @param[in] skip_callback whether we should skip calling the callback function; this is required after start_array() and start_object() SAX events, because otherwise we would call the callback function with an empty array or object, respectively. @invariant If the ref stack is empty, then the passed value will be the new root. @invariant If the ref stack contains a value, then it is an array or an object to which we can add elements @return pair of boolean (whether value should be kept) and pointer (to the passed value in the ref_stack hierarchy; nullptr if not kept) */ template std::pair handle_value(Value&& v, const bool skip_callback = false) { JSON_ASSERT(!keep_stack.empty()); // do not handle this value if we know it would be added to a discarded // container if (!keep_stack.back()) { return {false, nullptr}; } // create value auto value = BasicJsonType(std::forward(v)); // check callback const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); // do not handle this value if we just learnt it shall be discarded if (!keep) { return {false, nullptr}; } if (ref_stack.empty()) { root = std::move(value); return {true, &root}; } // skip this value if we already decided to skip the parent // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) if (!ref_stack.back()) { return {false, nullptr}; } // we now only expect arrays and objects JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); // array if (ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->emplace_back(std::move(value)); return {true, &(ref_stack.back()->m_value.array->back())}; } // object JSON_ASSERT(ref_stack.back()->is_object()); // check if we should store an element for the current key JSON_ASSERT(!key_keep_stack.empty()); const bool store_element = key_keep_stack.back(); key_keep_stack.pop_back(); if (!store_element) { return {false, nullptr}; } JSON_ASSERT(object_element); *object_element = std::move(value); return {true, object_element}; } /// the parsed JSON value BasicJsonType& root; /// stack to model hierarchy of values std::vector ref_stack {}; /// stack to manage which values to keep std::vector keep_stack {}; /// stack to manage which object keys to keep std::vector key_keep_stack {}; /// helper to hold the reference for the next object element BasicJsonType* object_element = nullptr; /// whether a syntax error occurred bool errored = false; /// callback function const parser_callback_t callback = nullptr; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; /// a discarded value for the callback BasicJsonType discarded = BasicJsonType::value_t::discarded; }; template class json_sax_acceptor { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; bool null() { return true; } bool boolean(bool /*unused*/) { return true; } bool number_integer(number_integer_t /*unused*/) { return true; } bool number_unsigned(number_unsigned_t /*unused*/) { return true; } bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) { return true; } bool string(string_t& /*unused*/) { return true; } bool binary(binary_t& /*unused*/) { return true; } bool start_object(std::size_t /*unused*/ = static_cast(-1)) { return true; } bool key(string_t& /*unused*/) { return true; } bool end_object() { return true; } bool start_array(std::size_t /*unused*/ = static_cast(-1)) { return true; } bool end_array() { return true; } bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) { return false; } }; } // namespace detail } // namespace nlohmann // #include #include // array #include // localeconv #include // size_t #include // snprintf #include // strtof, strtod, strtold, strtoll, strtoull #include // initializer_list #include // char_traits, string #include // move #include // vector // #include // #include // #include namespace nlohmann { namespace detail { /////////// // lexer // /////////// template class lexer_base { public: /// token types for the parser enum class token_type { uninitialized, ///< indicating the scanner is uninitialized literal_true, ///< the `true` literal literal_false, ///< the `false` literal literal_null, ///< the `null` literal value_string, ///< a string -- use get_string() for actual value value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value value_integer, ///< a signed integer -- use get_number_integer() for actual value value_float, ///< an floating point number -- use get_number_float() for actual value begin_array, ///< the character for array begin `[` begin_object, ///< the character for object begin `{` end_array, ///< the character for array end `]` end_object, ///< the character for object end `}` name_separator, ///< the name separator `:` value_separator, ///< the value separator `,` parse_error, ///< indicating a parse error end_of_input, ///< indicating the end of the input buffer literal_or_value ///< a literal or the begin of a value (only for diagnostics) }; /// return name of values of type token_type (only used for errors) JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_CONST static const char* token_type_name(const token_type t) noexcept { switch (t) { case token_type::uninitialized: return ""; case token_type::literal_true: return "true literal"; case token_type::literal_false: return "false literal"; case token_type::literal_null: return "null literal"; case token_type::value_string: return "string literal"; case token_type::value_unsigned: case token_type::value_integer: case token_type::value_float: return "number literal"; case token_type::begin_array: return "'['"; case token_type::begin_object: return "'{'"; case token_type::end_array: return "']'"; case token_type::end_object: return "'}'"; case token_type::name_separator: return "':'"; case token_type::value_separator: return "','"; case token_type::parse_error: return ""; case token_type::end_of_input: return "end of input"; case token_type::literal_or_value: return "'[', '{', or a literal"; // LCOV_EXCL_START default: // catch non-enum values return "unknown token"; // LCOV_EXCL_STOP } } }; /*! @brief lexical analysis This class organizes the lexical analysis during JSON deserialization. */ template class lexer : public lexer_base { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using char_type = typename InputAdapterType::char_type; using char_int_type = typename std::char_traits::int_type; public: using token_type = typename lexer_base::token_type; explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept : ia(std::move(adapter)) , ignore_comments(ignore_comments_) , decimal_point_char(static_cast(get_decimal_point())) {} // delete because of pointer members lexer(const lexer&) = delete; lexer(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) lexer& operator=(lexer&) = delete; lexer& operator=(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) ~lexer() = default; private: ///////////////////// // locales ///////////////////// /// return the locale-dependent decimal point JSON_HEDLEY_PURE static char get_decimal_point() noexcept { const auto* loc = localeconv(); JSON_ASSERT(loc != nullptr); return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); } ///////////////////// // scan functions ///////////////////// /*! @brief get codepoint from 4 hex characters following `\u` For input "\u c1 c2 c3 c4" the codepoint is: (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The conversion is done by subtracting the offset (0x30, 0x37, and 0x57) between the ASCII value of the character and the desired integer value. @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or non-hex character) */ int get_codepoint() { // this function only makes sense after reading `\u` JSON_ASSERT(current == 'u'); int codepoint = 0; const auto factors = { 12u, 8u, 4u, 0u }; for (const auto factor : factors) { get(); if (current >= '0' && current <= '9') { codepoint += static_cast((static_cast(current) - 0x30u) << factor); } else if (current >= 'A' && current <= 'F') { codepoint += static_cast((static_cast(current) - 0x37u) << factor); } else if (current >= 'a' && current <= 'f') { codepoint += static_cast((static_cast(current) - 0x57u) << factor); } else { return -1; } } JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); return codepoint; } /*! @brief check if the next byte(s) are inside a given range Adds the current byte and, for each passed range, reads a new byte and checks if it is inside the range. If a violation was detected, set up an error message and return false. Otherwise, return true. @param[in] ranges list of integers; interpreted as list of pairs of inclusive lower and upper bound, respectively @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, 1, 2, or 3 pairs. This precondition is enforced by an assertion. @return true if and only if no range violation was detected */ bool next_byte_in_range(std::initializer_list ranges) { JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); add(current); for (auto range = ranges.begin(); range != ranges.end(); ++range) { get(); if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) { add(current); } else { error_message = "invalid string: ill-formed UTF-8 byte"; return false; } } return true; } /*! @brief scan a string literal This function scans a string according to Sect. 7 of RFC 8259. While scanning, bytes are escaped and copied into buffer token_buffer. Then the function returns successfully, token_buffer is *not* null-terminated (as it may contain \0 bytes), and token_buffer.size() is the number of bytes in the string. @return token_type::value_string if string could be successfully scanned, token_type::parse_error otherwise @note In case of errors, variable error_message contains a textual description. */ token_type scan_string() { // reset token_buffer (ignore opening quote) reset(); // we entered the function by reading an open quote JSON_ASSERT(current == '\"'); while (true) { // get next character switch (get()) { // end of file while parsing string case std::char_traits::eof(): { error_message = "invalid string: missing closing quote"; return token_type::parse_error; } // closing quote case '\"': { return token_type::value_string; } // escapes case '\\': { switch (get()) { // quotation mark case '\"': add('\"'); break; // reverse solidus case '\\': add('\\'); break; // solidus case '/': add('/'); break; // backspace case 'b': add('\b'); break; // form feed case 'f': add('\f'); break; // line feed case 'n': add('\n'); break; // carriage return case 'r': add('\r'); break; // tab case 't': add('\t'); break; // unicode escapes case 'u': { const int codepoint1 = get_codepoint(); int codepoint = codepoint1; // start with codepoint1 if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) { error_message = "invalid string: '\\u' must be followed by 4 hex digits"; return token_type::parse_error; } // check if code point is a high surrogate if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) { // expect next \uxxxx entry if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) { const int codepoint2 = get_codepoint(); if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) { error_message = "invalid string: '\\u' must be followed by 4 hex digits"; return token_type::parse_error; } // check if codepoint2 is a low surrogate if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) { // overwrite codepoint codepoint = static_cast( // high surrogate occupies the most significant 22 bits (static_cast(codepoint1) << 10u) // low surrogate occupies the least significant 15 bits + static_cast(codepoint2) // there is still the 0xD800, 0xDC00 and 0x10000 noise // in the result, so we have to subtract with: // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - 0x35FDC00u); } else { error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) { error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; return token_type::parse_error; } } // result of the above calculation yields a proper codepoint JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); // translate codepoint into bytes if (codepoint < 0x80) { // 1-byte characters: 0xxxxxxx (ASCII) add(static_cast(codepoint)); } else if (codepoint <= 0x7FF) { // 2-byte characters: 110xxxxx 10xxxxxx add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } else if (codepoint <= 0xFFFF) { // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } else { // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } break; } // other characters after escape default: error_message = "invalid string: forbidden character after backslash"; return token_type::parse_error; } break; } // invalid control characters case 0x00: { error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; return token_type::parse_error; } case 0x01: { error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; return token_type::parse_error; } case 0x02: { error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; return token_type::parse_error; } case 0x03: { error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; return token_type::parse_error; } case 0x04: { error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; return token_type::parse_error; } case 0x05: { error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; return token_type::parse_error; } case 0x06: { error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; return token_type::parse_error; } case 0x07: { error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; return token_type::parse_error; } case 0x08: { error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; return token_type::parse_error; } case 0x09: { error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; return token_type::parse_error; } case 0x0A: { error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; return token_type::parse_error; } case 0x0B: { error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; return token_type::parse_error; } case 0x0C: { error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; return token_type::parse_error; } case 0x0D: { error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; return token_type::parse_error; } case 0x0E: { error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; return token_type::parse_error; } case 0x0F: { error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; return token_type::parse_error; } case 0x10: { error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; return token_type::parse_error; } case 0x11: { error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; return token_type::parse_error; } case 0x12: { error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; return token_type::parse_error; } case 0x13: { error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; return token_type::parse_error; } case 0x14: { error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; return token_type::parse_error; } case 0x15: { error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; return token_type::parse_error; } case 0x16: { error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; return token_type::parse_error; } case 0x17: { error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; return token_type::parse_error; } case 0x18: { error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; return token_type::parse_error; } case 0x19: { error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; return token_type::parse_error; } case 0x1A: { error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; return token_type::parse_error; } case 0x1B: { error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; return token_type::parse_error; } case 0x1C: { error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; return token_type::parse_error; } case 0x1D: { error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; return token_type::parse_error; } case 0x1E: { error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; return token_type::parse_error; } case 0x1F: { error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; return token_type::parse_error; } // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) case 0x20: case 0x21: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5D: case 0x5E: case 0x5F: case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: { add(current); break; } // U+0080..U+07FF: bytes C2..DF 80..BF case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: { if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) { return token_type::parse_error; } break; } // U+0800..U+0FFF: bytes E0 A0..BF 80..BF case 0xE0: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xEE: case 0xEF: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+D000..U+D7FF: bytes ED 80..9F 80..BF case 0xED: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF case 0xF0: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF case 0xF1: case 0xF2: case 0xF3: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF case 0xF4: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // remaining bytes (80..C1 and F5..FF) are ill-formed default: { error_message = "invalid string: ill-formed UTF-8 byte"; return token_type::parse_error; } } } } /*! * @brief scan a comment * @return whether comment could be scanned successfully */ bool scan_comment() { switch (get()) { // single-line comments skip input until a newline or EOF is read case '/': { while (true) { switch (get()) { case '\n': case '\r': case std::char_traits::eof(): case '\0': return true; default: break; } } } // multi-line comments skip input until */ is read case '*': { while (true) { switch (get()) { case std::char_traits::eof(): case '\0': { error_message = "invalid comment; missing closing '*/'"; return false; } case '*': { switch (get()) { case '/': return true; default: { unget(); continue; } } } default: continue; } } } // unexpected character after reading '/' default: { error_message = "invalid comment; expecting '/' or '*' after '/'"; return false; } } } JSON_HEDLEY_NON_NULL(2) static void strtof(float& f, const char* str, char** endptr) noexcept { f = std::strtof(str, endptr); } JSON_HEDLEY_NON_NULL(2) static void strtof(double& f, const char* str, char** endptr) noexcept { f = std::strtod(str, endptr); } JSON_HEDLEY_NON_NULL(2) static void strtof(long double& f, const char* str, char** endptr) noexcept { f = std::strtold(str, endptr); } /*! @brief scan a number literal This function scans a string according to Sect. 6 of RFC 8259. The function is realized with a deterministic finite state machine derived from the grammar described in RFC 8259. Starting in state "init", the input is read and used to determined the next state. Only state "done" accepts the number. State "error" is a trap state to model errors. In the table below, "anything" means any character but the ones listed before. state | 0 | 1-9 | e E | + | - | . | anything ---------|----------|----------|----------|---------|---------|----------|----------- init | zero | any1 | [error] | [error] | minus | [error] | [error] minus | zero | any1 | [error] | [error] | [error] | [error] | [error] zero | done | done | exponent | done | done | decimal1 | done any1 | any1 | any1 | exponent | done | done | decimal1 | done decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] decimal2 | decimal2 | decimal2 | exponent | done | done | done | done exponent | any2 | any2 | [error] | sign | sign | [error] | [error] sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] any2 | any2 | any2 | done | done | done | done | done The state machine is realized with one label per state (prefixed with "scan_number_") and `goto` statements between them. The state machine contains cycles, but any cycle can be left when EOF is read. Therefore, the function is guaranteed to terminate. During scanning, the read bytes are stored in token_buffer. This string is then converted to a signed integer, an unsigned integer, or a floating-point number. @return token_type::value_unsigned, token_type::value_integer, or token_type::value_float if number could be successfully scanned, token_type::parse_error otherwise @note The scanner is independent of the current locale. Internally, the locale's decimal point is used instead of `.` to work with the locale-dependent converters. */ token_type scan_number() // lgtm [cpp/use-of-goto] { // reset token_buffer to store the number's bytes reset(); // the type of the parsed number; initially set to unsigned; will be // changed if minus sign, decimal point or exponent is read token_type number_type = token_type::value_unsigned; // state (init): we just found out we need to scan a number switch (current) { case '-': { add(current); goto scan_number_minus; } case '0': { add(current); goto scan_number_zero; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } // all other characters are rejected outside scan_number() default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } scan_number_minus: // state: we just parsed a leading minus sign number_type = token_type::value_integer; switch (get()) { case '0': { add(current); goto scan_number_zero; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } default: { error_message = "invalid number; expected digit after '-'"; return token_type::parse_error; } } scan_number_zero: // state: we just parse a zero (maybe with a leading minus sign) switch (get()) { case '.': { add(decimal_point_char); goto scan_number_decimal1; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_any1: // state: we just parsed a number 0-9 (maybe with a leading minus sign) switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } case '.': { add(decimal_point_char); goto scan_number_decimal1; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_decimal1: // state: we just parsed a decimal point number_type = token_type::value_float; switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_decimal2; } default: { error_message = "invalid number; expected digit after '.'"; return token_type::parse_error; } } scan_number_decimal2: // we just parsed at least one number after a decimal point switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_decimal2; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_exponent: // we just parsed an exponent number_type = token_type::value_float; switch (get()) { case '+': case '-': { add(current); goto scan_number_sign; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: { error_message = "invalid number; expected '+', '-', or digit after exponent"; return token_type::parse_error; } } scan_number_sign: // we just parsed an exponent sign switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: { error_message = "invalid number; expected digit after exponent sign"; return token_type::parse_error; } } scan_number_any2: // we just parsed a number after the exponent or exponent sign switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: goto scan_number_done; } scan_number_done: // unget the character after the number (we only read it to know that // we are done scanning a number) unget(); char* endptr = nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) errno = 0; // try to parse integers first and fall back to floats if (number_type == token_type::value_unsigned) { const auto x = std::strtoull(token_buffer.data(), &endptr, 10); // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { value_unsigned = static_cast(x); if (value_unsigned == x) { return token_type::value_unsigned; } } } else if (number_type == token_type::value_integer) { const auto x = std::strtoll(token_buffer.data(), &endptr, 10); // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { value_integer = static_cast(x); if (value_integer == x) { return token_type::value_integer; } } } // this code is reached if we parse a floating-point number or if an // integer conversion above failed strtof(value_float, token_buffer.data(), &endptr); // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); return token_type::value_float; } /*! @param[in] literal_text the literal text to expect @param[in] length the length of the passed literal text @param[in] return_type the token type to return on success */ JSON_HEDLEY_NON_NULL(2) token_type scan_literal(const char_type* literal_text, const std::size_t length, token_type return_type) { JSON_ASSERT(std::char_traits::to_char_type(current) == literal_text[0]); for (std::size_t i = 1; i < length; ++i) { if (JSON_HEDLEY_UNLIKELY(std::char_traits::to_char_type(get()) != literal_text[i])) { error_message = "invalid literal"; return token_type::parse_error; } } return return_type; } ///////////////////// // input management ///////////////////// /// reset token_buffer; current character is beginning of token void reset() noexcept { token_buffer.clear(); token_string.clear(); token_string.push_back(std::char_traits::to_char_type(current)); } /* @brief get next character from the input This function provides the interface to the used input adapter. It does not throw in case the input reached EOF, but returns a `std::char_traits::eof()` in that case. Stores the scanned characters for use in error messages. @return character read from the input */ char_int_type get() { ++position.chars_read_total; ++position.chars_read_current_line; if (next_unget) { // just reset the next_unget variable and work with current next_unget = false; } else { current = ia.get_character(); } if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) { token_string.push_back(std::char_traits::to_char_type(current)); } if (current == '\n') { ++position.lines_read; position.chars_read_current_line = 0; } return current; } /*! @brief unget current character (read it again on next get) We implement unget by setting variable next_unget to true. The input is not changed - we just simulate ungetting by modifying chars_read_total, chars_read_current_line, and token_string. The next call to get() will behave as if the unget character is read again. */ void unget() { next_unget = true; --position.chars_read_total; // in case we "unget" a newline, we have to also decrement the lines_read if (position.chars_read_current_line == 0) { if (position.lines_read > 0) { --position.lines_read; } } else { --position.chars_read_current_line; } if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) { JSON_ASSERT(!token_string.empty()); token_string.pop_back(); } } /// add a character to token_buffer void add(char_int_type c) { token_buffer.push_back(static_cast(c)); } public: ///////////////////// // value getters ///////////////////// /// return integer value constexpr number_integer_t get_number_integer() const noexcept { return value_integer; } /// return unsigned integer value constexpr number_unsigned_t get_number_unsigned() const noexcept { return value_unsigned; } /// return floating-point value constexpr number_float_t get_number_float() const noexcept { return value_float; } /// return current string value (implicitly resets the token; useful only once) string_t& get_string() { return token_buffer; } ///////////////////// // diagnostics ///////////////////// /// return position of last read token constexpr position_t get_position() const noexcept { return position; } /// return the last read token (for errors only). Will never contain EOF /// (an arbitrary value that is not a valid char value, often -1), because /// 255 may legitimately occur. May contain NUL, which should be escaped. std::string get_token_string() const { // escape control characters std::string result; for (const auto c : token_string) { if (static_cast(c) <= '\x1F') { // escape control characters std::array cs{{}}; static_cast((std::snprintf)(cs.data(), cs.size(), "", static_cast(c))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) result += cs.data(); } else { // add character as is result.push_back(static_cast(c)); } } return result; } /// return syntax error message JSON_HEDLEY_RETURNS_NON_NULL constexpr const char* get_error_message() const noexcept { return error_message; } ///////////////////// // actual scanner ///////////////////// /*! @brief skip the UTF-8 byte order mark @return true iff there is no BOM or the correct BOM has been skipped */ bool skip_bom() { if (get() == 0xEF) { // check if we completely parse the BOM return get() == 0xBB && get() == 0xBF; } // the first character is not the beginning of the BOM; unget it to // process is later unget(); return true; } void skip_whitespace() { do { get(); } while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); } token_type scan() { // initially, skip the BOM if (position.chars_read_total == 0 && !skip_bom()) { error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; return token_type::parse_error; } // read next character and ignore whitespace skip_whitespace(); // ignore comments while (ignore_comments && current == '/') { if (!scan_comment()) { return token_type::parse_error; } // skip following whitespace skip_whitespace(); } switch (current) { // structural characters case '[': return token_type::begin_array; case ']': return token_type::end_array; case '{': return token_type::begin_object; case '}': return token_type::end_object; case ':': return token_type::name_separator; case ',': return token_type::value_separator; // literals case 't': { std::array true_literal = {{static_cast('t'), static_cast('r'), static_cast('u'), static_cast('e')}}; return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); } case 'f': { std::array false_literal = {{static_cast('f'), static_cast('a'), static_cast('l'), static_cast('s'), static_cast('e')}}; return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); } case 'n': { std::array null_literal = {{static_cast('n'), static_cast('u'), static_cast('l'), static_cast('l')}}; return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); } // string case '\"': return scan_string(); // number case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return scan_number(); // end of input (the null byte is needed when parsing from // string literals) case '\0': case std::char_traits::eof(): return token_type::end_of_input; // error default: error_message = "invalid literal"; return token_type::parse_error; } } private: /// input adapter InputAdapterType ia; /// whether comments should be ignored (true) or signaled as errors (false) const bool ignore_comments = false; /// the current character char_int_type current = std::char_traits::eof(); /// whether the next get() call should just return current bool next_unget = false; /// the start position of the current token position_t position {}; /// raw input token string (for error messages) std::vector token_string {}; /// buffer for variable-length tokens (numbers, strings) string_t token_buffer {}; /// a description of occurred lexer errors const char* error_message = ""; // number values number_integer_t value_integer = 0; number_unsigned_t value_unsigned = 0; number_float_t value_float = 0; /// the decimal point const char_int_type decimal_point_char = '.'; }; } // namespace detail } // namespace nlohmann // #include // #include #include // size_t #include // declval #include // string // #include // #include namespace nlohmann { namespace detail { template using null_function_t = decltype(std::declval().null()); template using boolean_function_t = decltype(std::declval().boolean(std::declval())); template using number_integer_function_t = decltype(std::declval().number_integer(std::declval())); template using number_unsigned_function_t = decltype(std::declval().number_unsigned(std::declval())); template using number_float_function_t = decltype(std::declval().number_float( std::declval(), std::declval())); template using string_function_t = decltype(std::declval().string(std::declval())); template using binary_function_t = decltype(std::declval().binary(std::declval())); template using start_object_function_t = decltype(std::declval().start_object(std::declval())); template using key_function_t = decltype(std::declval().key(std::declval())); template using end_object_function_t = decltype(std::declval().end_object()); template using start_array_function_t = decltype(std::declval().start_array(std::declval())); template using end_array_function_t = decltype(std::declval().end_array()); template using parse_error_function_t = decltype(std::declval().parse_error( std::declval(), std::declval(), std::declval())); template struct is_sax { private: static_assert(is_basic_json::value, "BasicJsonType must be of type basic_json<...>"); using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using exception_t = typename BasicJsonType::exception; public: static constexpr bool value = is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value; }; template struct is_sax_static_asserts { private: static_assert(is_basic_json::value, "BasicJsonType must be of type basic_json<...>"); using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using exception_t = typename BasicJsonType::exception; public: static_assert(is_detected_exact::value, "Missing/invalid function: bool null()"); static_assert(is_detected_exact::value, "Missing/invalid function: bool boolean(bool)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool boolean(bool)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool number_integer(number_integer_t)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool number_unsigned(number_unsigned_t)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool number_float(number_float_t, const string_t&)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool string(string_t&)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool binary(binary_t&)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool start_object(std::size_t)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool key(string_t&)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool end_object()"); static_assert(is_detected_exact::value, "Missing/invalid function: bool start_array(std::size_t)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool end_array()"); static_assert( is_detected_exact::value, "Missing/invalid function: bool parse_error(std::size_t, const " "std::string&, const exception&)"); }; } // namespace detail } // namespace nlohmann // #include // #include namespace nlohmann { namespace detail { /// how to treat CBOR tags enum class cbor_tag_handler_t { error, ///< throw a parse_error exception in case of a tag ignore, ///< ignore tags store ///< store tags as binary type }; /*! @brief determine system byte order @return true if and only if system's byte order is little endian @note from https://stackoverflow.com/a/1001328/266378 */ static inline bool little_endianness(int num = 1) noexcept { return *reinterpret_cast(&num) == 1; } /////////////////// // binary reader // /////////////////// /*! @brief deserialization of CBOR, MessagePack, and UBJSON values */ template> class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using json_sax_t = SAX; using char_type = typename InputAdapterType::char_type; using char_int_type = typename std::char_traits::int_type; public: /*! @brief create a binary reader @param[in] adapter input adapter to read from */ explicit binary_reader(InputAdapterType&& adapter) noexcept : ia(std::move(adapter)) { (void)detail::is_sax_static_asserts {}; } // make class move-only binary_reader(const binary_reader&) = delete; binary_reader(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) binary_reader& operator=(const binary_reader&) = delete; binary_reader& operator=(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) ~binary_reader() = default; /*! @param[in] format the binary format to parse @param[in] sax_ a SAX event processor @param[in] strict whether to expect the input to be consumed completed @param[in] tag_handler how to treat CBOR tags @return whether parsing was successful */ JSON_HEDLEY_NON_NULL(3) bool sax_parse(const input_format_t format, json_sax_t* sax_, const bool strict = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { sax = sax_; bool result = false; switch (format) { case input_format_t::bson: result = parse_bson_internal(); break; case input_format_t::cbor: result = parse_cbor_internal(true, tag_handler); break; case input_format_t::msgpack: result = parse_msgpack_internal(); break; case input_format_t::ubjson: result = parse_ubjson_internal(); break; case input_format_t::json: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } // strict mode: next byte must be EOF if (result && strict) { if (format == input_format_t::ubjson) { get_ignore_noop(); } else { get(); } if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) { return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value"), BasicJsonType())); } } return result; } private: ////////// // BSON // ////////// /*! @brief Reads in a BSON-object and passes it to the SAX-parser. @return whether a valid BSON-value was passed to the SAX parser */ bool parse_bson_internal() { std::int32_t document_size{}; get_number(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false))) { return false; } return sax->end_object(); } /*! @brief Parses a C-style string from the BSON input. @param[in,out] result A reference to the string variable where the read string is to be stored. @return `true` if the \x00-byte indicating the end of the string was encountered before the EOF; false` indicates an unexpected EOF. */ bool get_bson_cstr(string_t& result) { auto out = std::back_inserter(result); while (true) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "cstring"))) { return false; } if (current == 0x00) { return true; } *out++ = static_cast(current); } } /*! @brief Parses a zero-terminated string of length @a len from the BSON input. @param[in] len The length (including the zero-byte at the end) of the string to be read. @param[in,out] result A reference to the string variable where the read string is to be stored. @tparam NumberType The type of the length @a len @pre len >= 1 @return `true` if the string was successfully parsed */ template bool get_bson_string(const NumberType len, string_t& result) { if (JSON_HEDLEY_UNLIKELY(len < 1)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string"), BasicJsonType())); } return get_string(input_format_t::bson, len - static_cast(1), result) && get() != std::char_traits::eof(); } /*! @brief Parses a byte array input of length @a len from the BSON input. @param[in] len The length of the byte array to be read. @param[in,out] result A reference to the binary variable where the read array is to be stored. @tparam NumberType The type of the length @a len @pre len >= 0 @return `true` if the byte array was successfully parsed */ template bool get_bson_binary(const NumberType len, binary_t& result) { if (JSON_HEDLEY_UNLIKELY(len < 0)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "byte array length cannot be negative, is " + std::to_string(len), "binary"), BasicJsonType())); } // All BSON binary values have a subtype std::uint8_t subtype{}; get_number(input_format_t::bson, subtype); result.set_subtype(subtype); return get_binary(input_format_t::bson, len, result); } /*! @brief Read a BSON document element of the given @a element_type. @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html @param[in] element_type_parse_position The position in the input stream, where the `element_type` was read. @warning Not all BSON element types are supported yet. An unsupported @a element_type will give rise to a parse_error.114: Unsupported BSON record type 0x... @return whether a valid BSON-object/array was passed to the SAX parser */ bool parse_bson_element_internal(const char_int_type element_type, const std::size_t element_type_parse_position) { switch (element_type) { case 0x01: // double { double number{}; return get_number(input_format_t::bson, number) && sax->number_float(static_cast(number), ""); } case 0x02: // string { std::int32_t len{}; string_t value; return get_number(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value); } case 0x03: // object { return parse_bson_internal(); } case 0x04: // array { return parse_bson_array(); } case 0x05: // binary { std::int32_t len{}; binary_t value; return get_number(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value); } case 0x08: // boolean { return sax->boolean(get() != 0); } case 0x0A: // null { return sax->null(); } case 0x10: // int32 { std::int32_t value{}; return get_number(input_format_t::bson, value) && sax->number_integer(value); } case 0x12: // int64 { std::int64_t value{}; return get_number(input_format_t::bson, value) && sax->number_integer(value); } default: // anything else not supported (yet) { std::array cr{{}}; static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) return sax->parse_error(element_type_parse_position, std::string(cr.data()), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr.data()), BasicJsonType())); } } } /*! @brief Read a BSON element list (as specified in the BSON-spec) The same binary layout is used for objects and arrays, hence it must be indicated with the argument @a is_array which one is expected (true --> array, false --> object). @param[in] is_array Determines if the element list being read is to be treated as an object (@a is_array == false), or as an array (@a is_array == true). @return whether a valid BSON-object/array was passed to the SAX parser */ bool parse_bson_element_list(const bool is_array) { string_t key; while (auto element_type = get()) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "element list"))) { return false; } const std::size_t element_type_parse_position = chars_read; if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) { return false; } if (!is_array && !sax->key(key)) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position))) { return false; } // get_bson_cstr only appends key.clear(); } return true; } /*! @brief Reads an array from the BSON input and passes it to the SAX-parser. @return whether a valid BSON-array was passed to the SAX parser */ bool parse_bson_array() { std::int32_t document_size{}; get_number(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) { return false; } return sax->end_array(); } ////////// // CBOR // ////////// /*! @param[in] get_char whether a new character should be retrieved from the input (true) or whether the last read character should be considered instead (false) @param[in] tag_handler how CBOR tags should be treated @return whether a valid CBOR value was passed to the SAX parser */ bool parse_cbor_internal(const bool get_char, const cbor_tag_handler_t tag_handler) { switch (get_char ? get() : current) { // EOF case std::char_traits::eof(): return unexpect_eof(input_format_t::cbor, "value"); // Integer 0x00..0x17 (0..23) case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: return sax->number_unsigned(static_cast(current)); case 0x18: // Unsigned integer (one-byte uint8_t follows) { std::uint8_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } case 0x19: // Unsigned integer (two-byte uint16_t follows) { std::uint16_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } case 0x1A: // Unsigned integer (four-byte uint32_t follows) { std::uint32_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } case 0x1B: // Unsigned integer (eight-byte uint64_t follows) { std::uint64_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } // Negative integer -1-0x00..-1-0x17 (-1..-24) case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: return sax->number_integer(static_cast(0x20 - 1 - current)); case 0x38: // Negative integer (one-byte uint8_t follows) { std::uint8_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); } case 0x39: // Negative integer -1-n (two-byte uint16_t follows) { std::uint16_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); } case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) { std::uint32_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); } case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) { std::uint64_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - static_cast(number)); } // Binary data (0x00..0x17 bytes follow) case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: // Binary data (one-byte uint8_t for n follows) case 0x59: // Binary data (two-byte uint16_t for n follow) case 0x5A: // Binary data (four-byte uint32_t for n follow) case 0x5B: // Binary data (eight-byte uint64_t for n follow) case 0x5F: // Binary data (indefinite length) { binary_t b; return get_cbor_binary(b) && sax->binary(b); } // UTF-8 string (0x00..0x17 bytes follow) case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: // UTF-8 string (one-byte uint8_t for n follows) case 0x79: // UTF-8 string (two-byte uint16_t for n follow) case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) case 0x7F: // UTF-8 string (indefinite length) { string_t s; return get_cbor_string(s) && sax->string(s); } // array (0x00..0x17 data items follow) case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: return get_cbor_array(static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0x98: // array (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x99: // array (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x9A: // array (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x9B: // array (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(detail::conditional_static_cast(len), tag_handler); } case 0x9F: // array (indefinite length) return get_cbor_array(static_cast(-1), tag_handler); // map (0x00..0x17 pairs of data items follow) case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: return get_cbor_object(static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0xB8: // map (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xB9: // map (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xBA: // map (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xBB: // map (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(detail::conditional_static_cast(len), tag_handler); } case 0xBF: // map (indefinite length) return get_cbor_object(static_cast(-1), tag_handler); case 0xC6: // tagged item case 0xC7: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD8: // tagged item (1 bytes follow) case 0xD9: // tagged item (2 bytes follow) case 0xDA: // tagged item (4 bytes follow) case 0xDB: // tagged item (8 bytes follow) { switch (tag_handler) { case cbor_tag_handler_t::error: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"), BasicJsonType())); } case cbor_tag_handler_t::ignore: { // ignore binary subtype switch (current) { case 0xD8: { std::uint8_t subtype_to_ignore{}; get_number(input_format_t::cbor, subtype_to_ignore); break; } case 0xD9: { std::uint16_t subtype_to_ignore{}; get_number(input_format_t::cbor, subtype_to_ignore); break; } case 0xDA: { std::uint32_t subtype_to_ignore{}; get_number(input_format_t::cbor, subtype_to_ignore); break; } case 0xDB: { std::uint64_t subtype_to_ignore{}; get_number(input_format_t::cbor, subtype_to_ignore); break; } default: break; } return parse_cbor_internal(true, tag_handler); } case cbor_tag_handler_t::store: { binary_t b; // use binary subtype and store in binary container switch (current) { case 0xD8: { std::uint8_t subtype{}; get_number(input_format_t::cbor, subtype); b.set_subtype(detail::conditional_static_cast(subtype)); break; } case 0xD9: { std::uint16_t subtype{}; get_number(input_format_t::cbor, subtype); b.set_subtype(detail::conditional_static_cast(subtype)); break; } case 0xDA: { std::uint32_t subtype{}; get_number(input_format_t::cbor, subtype); b.set_subtype(detail::conditional_static_cast(subtype)); break; } case 0xDB: { std::uint64_t subtype{}; get_number(input_format_t::cbor, subtype); b.set_subtype(detail::conditional_static_cast(subtype)); break; } default: return parse_cbor_internal(true, tag_handler); } get(); return get_cbor_binary(b) && sax->binary(b); } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE } } case 0xF4: // false return sax->boolean(false); case 0xF5: // true return sax->boolean(true); case 0xF6: // null return sax->null(); case 0xF9: // Half-Precision Float (two-byte IEEE 754) { const auto byte1_raw = get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) { return false; } const auto byte2_raw = get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) { return false; } const auto byte1 = static_cast(byte1_raw); const auto byte2 = static_cast(byte2_raw); // code from RFC 7049, Appendix D, Figure 3: // As half-precision floating-point numbers were only added // to IEEE 754 in 2008, today's programming platforms often // still only have limited support for them. It is very // easy to include at least decoding support for them even // without such support. An example of a small decoder for // half-precision floating-point numbers in the C language // is shown in Fig. 3. const auto half = static_cast((byte1 << 8u) + byte2); const double val = [&half] { const int exp = (half >> 10u) & 0x1Fu; const unsigned int mant = half & 0x3FFu; JSON_ASSERT(0 <= exp&& exp <= 32); JSON_ASSERT(mant <= 1024); switch (exp) { case 0: return std::ldexp(mant, -24); case 31: return (mant == 0) ? std::numeric_limits::infinity() : std::numeric_limits::quiet_NaN(); default: return std::ldexp(mant + 1024, exp - 25); } }(); return sax->number_float((half & 0x8000u) != 0 ? static_cast(-val) : static_cast(val), ""); } case 0xFA: // Single-Precision Float (four-byte IEEE 754) { float number{}; return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); } case 0xFB: // Double-Precision Float (eight-byte IEEE 754) { double number{}; return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); } default: // anything else (0xFF is handled inside the other types) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"), BasicJsonType())); } } } /*! @brief reads a CBOR string This function first reads starting bytes to determine the expected string length and then copies this number of bytes into a string. Additionally, CBOR's strings with indefinite lengths are supported. @param[out] result created string @return whether string creation completed */ bool get_cbor_string(string_t& result) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string"))) { return false; } switch (current) { // UTF-8 string (0x00..0x17 bytes follow) case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: { return get_string(input_format_t::cbor, static_cast(current) & 0x1Fu, result); } case 0x78: // UTF-8 string (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x79: // UTF-8 string (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x7F: // UTF-8 string (indefinite length) { while (get() != 0xFF) { string_t chunk; if (!get_cbor_string(chunk)) { return false; } result.append(chunk); } return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string"), BasicJsonType())); } } } /*! @brief reads a CBOR byte array This function first reads starting bytes to determine the expected byte array length and then copies this number of bytes into the byte array. Additionally, CBOR's byte arrays with indefinite lengths are supported. @param[out] result created byte array @return whether byte array creation completed */ bool get_cbor_binary(binary_t& result) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary"))) { return false; } switch (current) { // Binary data (0x00..0x17 bytes follow) case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: { return get_binary(input_format_t::cbor, static_cast(current) & 0x1Fu, result); } case 0x58: // Binary data (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x59: // Binary data (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x5A: // Binary data (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x5B: // Binary data (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x5F: // Binary data (indefinite length) { while (get() != 0xFF) { binary_t chunk; if (!get_cbor_binary(chunk)) { return false; } result.insert(result.end(), chunk.begin(), chunk.end()); } return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x" + last_token, "binary"), BasicJsonType())); } } } /*! @param[in] len the length of the array or static_cast(-1) for an array of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether array creation completed */ bool get_cbor_array(const std::size_t len, const cbor_tag_handler_t tag_handler) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) { return false; } if (len != static_cast(-1)) { for (std::size_t i = 0; i < len; ++i) { if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) { return false; } } } else { while (get() != 0xFF) { if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) { return false; } } } return sax->end_array(); } /*! @param[in] len the length of the object or static_cast(-1) for an object of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether object creation completed */ bool get_cbor_object(const std::size_t len, const cbor_tag_handler_t tag_handler) { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) { return false; } if (len != 0) { string_t key; if (len != static_cast(-1)) { for (std::size_t i = 0; i < len; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) { return false; } key.clear(); } } else { while (get() != 0xFF) { if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) { return false; } key.clear(); } } } return sax->end_object(); } ///////////// // MsgPack // ///////////// /*! @return whether a valid MessagePack value was passed to the SAX parser */ bool parse_msgpack_internal() { switch (get()) { // EOF case std::char_traits::eof(): return unexpect_eof(input_format_t::msgpack, "value"); // positive fixint case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: return sax->number_unsigned(static_cast(current)); // fixmap case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: return get_msgpack_object(static_cast(static_cast(current) & 0x0Fu)); // fixarray case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: return get_msgpack_array(static_cast(static_cast(current) & 0x0Fu)); // fixstr case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xD9: // str 8 case 0xDA: // str 16 case 0xDB: // str 32 { string_t s; return get_msgpack_string(s) && sax->string(s); } case 0xC0: // nil return sax->null(); case 0xC2: // false return sax->boolean(false); case 0xC3: // true return sax->boolean(true); case 0xC4: // bin 8 case 0xC5: // bin 16 case 0xC6: // bin 32 case 0xC7: // ext 8 case 0xC8: // ext 16 case 0xC9: // ext 32 case 0xD4: // fixext 1 case 0xD5: // fixext 2 case 0xD6: // fixext 4 case 0xD7: // fixext 8 case 0xD8: // fixext 16 { binary_t b; return get_msgpack_binary(b) && sax->binary(b); } case 0xCA: // float 32 { float number{}; return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); } case 0xCB: // float 64 { double number{}; return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); } case 0xCC: // uint 8 { std::uint8_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xCD: // uint 16 { std::uint16_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xCE: // uint 32 { std::uint32_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xCF: // uint 64 { std::uint64_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xD0: // int 8 { std::int8_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xD1: // int 16 { std::int16_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xD2: // int 32 { std::int32_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xD3: // int 64 { std::int64_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xDC: // array 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); } case 0xDD: // array 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); } case 0xDE: // map 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); } case 0xDF: // map 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); } // negative fixint case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: return sax->number_integer(static_cast(current)); default: // anything else { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value"), BasicJsonType())); } } } /*! @brief reads a MessagePack string This function first reads starting bytes to determine the expected string length and then copies this number of bytes into a string. @param[out] result created string @return whether string creation completed */ bool get_msgpack_string(string_t& result) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) { return false; } switch (current) { // fixstr case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: { return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); } case 0xD9: // str 8 { std::uint8_t len{}; return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); } case 0xDA: // str 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); } case 0xDB: // str 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string"), BasicJsonType())); } } } /*! @brief reads a MessagePack byte array This function first reads starting bytes to determine the expected byte array length and then copies this number of bytes into a byte array. @param[out] result created byte array @return whether byte array creation completed */ bool get_msgpack_binary(binary_t& result) { // helper function to set the subtype auto assign_and_return_true = [&result](std::int8_t subtype) { result.set_subtype(static_cast(subtype)); return true; }; switch (current) { case 0xC4: // bin 8 { std::uint8_t len{}; return get_number(input_format_t::msgpack, len) && get_binary(input_format_t::msgpack, len, result); } case 0xC5: // bin 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_binary(input_format_t::msgpack, len, result); } case 0xC6: // bin 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_binary(input_format_t::msgpack, len, result); } case 0xC7: // ext 8 { std::uint8_t len{}; std::int8_t subtype{}; return get_number(input_format_t::msgpack, len) && get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, len, result) && assign_and_return_true(subtype); } case 0xC8: // ext 16 { std::uint16_t len{}; std::int8_t subtype{}; return get_number(input_format_t::msgpack, len) && get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, len, result) && assign_and_return_true(subtype); } case 0xC9: // ext 32 { std::uint32_t len{}; std::int8_t subtype{}; return get_number(input_format_t::msgpack, len) && get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, len, result) && assign_and_return_true(subtype); } case 0xD4: // fixext 1 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 1, result) && assign_and_return_true(subtype); } case 0xD5: // fixext 2 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 2, result) && assign_and_return_true(subtype); } case 0xD6: // fixext 4 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 4, result) && assign_and_return_true(subtype); } case 0xD7: // fixext 8 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 8, result) && assign_and_return_true(subtype); } case 0xD8: // fixext 16 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 16, result) && assign_and_return_true(subtype); } default: // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE } } /*! @param[in] len the length of the array @return whether array creation completed */ bool get_msgpack_array(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) { return false; } for (std::size_t i = 0; i < len; ++i) { if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) { return false; } } return sax->end_array(); } /*! @param[in] len the length of the object @return whether object creation completed */ bool get_msgpack_object(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) { return false; } string_t key; for (std::size_t i = 0; i < len; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) { return false; } key.clear(); } return sax->end_object(); } //////////// // UBJSON // //////////// /*! @param[in] get_char whether a new character should be retrieved from the input (true, default) or whether the last read character should be considered instead @return whether a valid UBJSON value was passed to the SAX parser */ bool parse_ubjson_internal(const bool get_char = true) { return get_ubjson_value(get_char ? get_ignore_noop() : current); } /*! @brief reads a UBJSON string This function is either called after reading the 'S' byte explicitly indicating a string, or in case of an object key where the 'S' byte can be left out. @param[out] result created string @param[in] get_char whether a new character should be retrieved from the input (true, default) or whether the last read character should be considered instead @return whether string creation completed */ bool get_ubjson_string(string_t& result, const bool get_char = true) { if (get_char) { get(); // TODO(niels): may we ignore N here? } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) { return false; } switch (current) { case 'U': { std::uint8_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'i': { std::int8_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'I': { std::int16_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'l': { std::int32_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'L': { std::int64_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } default: auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string"), BasicJsonType())); } } /*! @param[out] result determined size @return whether size determination completed */ bool get_ubjson_size_value(std::size_t& result) { switch (get_ignore_noop()) { case 'U': { std::uint8_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'i': { std::int8_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char return true; } case 'I': { std::int16_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'l': { std::int32_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'L': { std::int64_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size"), BasicJsonType())); } } } /*! @brief determine the type and size for a container In the optimized UBJSON format, a type and a size can be provided to allow for a more compact representation. @param[out] result pair of the size and the type @return whether pair creation completed */ bool get_ubjson_size_type(std::pair& result) { result.first = string_t::npos; // size result.second = 0; // type get_ignore_noop(); if (current == '$') { result.second = get(); // must not ignore 'N', because 'N' maybe the type if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type"))) { return false; } get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) { return false; } auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size"), BasicJsonType())); } return get_ubjson_size_value(result.first); } if (current == '#') { return get_ubjson_size_value(result.first); } return true; } /*! @param prefix the previously read or set type prefix @return whether value creation completed */ bool get_ubjson_value(const char_int_type prefix) { switch (prefix) { case std::char_traits::eof(): // EOF return unexpect_eof(input_format_t::ubjson, "value"); case 'T': // true return sax->boolean(true); case 'F': // false return sax->boolean(false); case 'Z': // null return sax->null(); case 'U': { std::uint8_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number); } case 'i': { std::int8_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'I': { std::int16_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'l': { std::int32_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'L': { std::int64_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'd': { float number{}; return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); } case 'D': { double number{}; return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); } case 'H': { return get_ubjson_high_precision_number(); } case 'C': // char { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char"))) { return false; } if (JSON_HEDLEY_UNLIKELY(current > 127)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char"), BasicJsonType())); } string_t s(1, static_cast(current)); return sax->string(s); } case 'S': // string { string_t s; return get_ubjson_string(s) && sax->string(s); } case '[': // array return get_ubjson_array(); case '{': // object return get_ubjson_object(); default: // anything else { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value"), BasicJsonType())); } } } /*! @return whether array creation completed */ bool get_ubjson_array() { std::pair size_and_type; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { return false; } if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) { return false; } if (size_and_type.second != 0) { if (size_and_type.second != 'N') { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; } } } } else { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) { return false; } } } } else { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) { return false; } while (current != ']') { if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) { return false; } get_ignore_noop(); } } return sax->end_array(); } /*! @return whether object creation completed */ bool get_ubjson_object() { std::pair size_and_type; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { return false; } string_t key; if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) { return false; } if (size_and_type.second != 0) { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; } key.clear(); } } else { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) { return false; } key.clear(); } } } else { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) { return false; } while (current != '}') { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) { return false; } get_ignore_noop(); key.clear(); } } return sax->end_object(); } // Note, no reader for UBJSON binary types is implemented because they do // not exist bool get_ubjson_high_precision_number() { // get size of following number string std::size_t size{}; auto res = get_ubjson_size_value(size); if (JSON_HEDLEY_UNLIKELY(!res)) { return res; } // get number string std::vector number_vector; for (std::size_t i = 0; i < size; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number"))) { return false; } number_vector.push_back(static_cast(current)); } // parse number string using ia_type = decltype(detail::input_adapter(number_vector)); auto number_lexer = detail::lexer(detail::input_adapter(number_vector), false); const auto result_number = number_lexer.scan(); const auto number_string = number_lexer.get_token_string(); const auto result_remainder = number_lexer.scan(); using token_type = typename detail::lexer_base::token_type; if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) { return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"), BasicJsonType())); } switch (result_number) { case token_type::value_integer: return sax->number_integer(number_lexer.get_number_integer()); case token_type::value_unsigned: return sax->number_unsigned(number_lexer.get_number_unsigned()); case token_type::value_float: return sax->number_float(number_lexer.get_number_float(), std::move(number_string)); case token_type::uninitialized: case token_type::literal_true: case token_type::literal_false: case token_type::literal_null: case token_type::value_string: case token_type::begin_array: case token_type::begin_object: case token_type::end_array: case token_type::end_object: case token_type::name_separator: case token_type::value_separator: case token_type::parse_error: case token_type::end_of_input: case token_type::literal_or_value: default: return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"), BasicJsonType())); } } /////////////////////// // Utility functions // /////////////////////// /*! @brief get next character from the input This function provides the interface to the used input adapter. It does not throw in case the input reached EOF, but returns a -'ve valued `std::char_traits::eof()` in that case. @return character read from the input */ char_int_type get() { ++chars_read; return current = ia.get_character(); } /*! @return character read from the input after ignoring all 'N' entries */ char_int_type get_ignore_noop() { do { get(); } while (current == 'N'); return current; } /* @brief read a number from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[out] result number of type @a NumberType @return whether conversion completed @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ template bool get_number(const input_format_t format, NumberType& result) { // step 1: read input into array with system's byte order std::array vec{}; for (std::size_t i = 0; i < sizeof(NumberType); ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) { return false; } // reverse byte order prior to conversion if necessary if (is_little_endian != InputIsLittleEndian) { vec[sizeof(NumberType) - i - 1] = static_cast(current); } else { vec[i] = static_cast(current); // LCOV_EXCL_LINE } } // step 2: convert array into number of type T and return std::memcpy(&result, vec.data(), sizeof(NumberType)); return true; } /*! @brief create a string by reading characters from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[in] len number of characters to read @param[out] result string created by reading @a len bytes @return whether string creation completed @note We can not reserve @a len bytes for the result, because @a len may be too large. Usually, @ref unexpect_eof() detects the end of the input before we run out of string memory. */ template bool get_string(const input_format_t format, const NumberType len, string_t& result) { bool success = true; for (NumberType i = 0; i < len; i++) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string"))) { success = false; break; } result.push_back(static_cast(current)); } return success; } /*! @brief create a byte array by reading bytes from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[in] len number of bytes to read @param[out] result byte array created by reading @a len bytes @return whether byte array creation completed @note We can not reserve @a len bytes for the result, because @a len may be too large. Usually, @ref unexpect_eof() detects the end of the input before we run out of memory. */ template bool get_binary(const input_format_t format, const NumberType len, binary_t& result) { bool success = true; for (NumberType i = 0; i < len; i++) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary"))) { success = false; break; } result.push_back(static_cast(current)); } return success; } /*! @param[in] format the current format (for diagnostics) @param[in] context further context information (for diagnostics) @return whether the last read character is not EOF */ JSON_HEDLEY_NON_NULL(3) bool unexpect_eof(const input_format_t format, const char* context) const { if (JSON_HEDLEY_UNLIKELY(current == std::char_traits::eof())) { return sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), BasicJsonType())); } return true; } /*! @return a string representation of the last read byte */ std::string get_token_string() const { std::array cr{{}}; static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) return std::string{cr.data()}; } /*! @param[in] format the current format @param[in] detail a detailed error message @param[in] context further context information @return a message string to use in the parse_error exceptions */ std::string exception_message(const input_format_t format, const std::string& detail, const std::string& context) const { std::string error_msg = "syntax error while parsing "; switch (format) { case input_format_t::cbor: error_msg += "CBOR"; break; case input_format_t::msgpack: error_msg += "MessagePack"; break; case input_format_t::ubjson: error_msg += "UBJSON"; break; case input_format_t::bson: error_msg += "BSON"; break; case input_format_t::json: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } return error_msg + " " + context + ": " + detail; } private: /// input adapter InputAdapterType ia; /// the current character char_int_type current = std::char_traits::eof(); /// the number of characters read std::size_t chars_read = 0; /// whether we can assume little endianness const bool is_little_endian = little_endianness(); /// the SAX parser json_sax_t* sax = nullptr; }; } // namespace detail } // namespace nlohmann // #include // #include // #include #include // isfinite #include // uint8_t #include // function #include // string #include // move #include // vector // #include // #include // #include // #include // #include // #include // #include namespace nlohmann { namespace detail { //////////// // parser // //////////// enum class parse_event_t : std::uint8_t { /// the parser read `{` and started to process a JSON object object_start, /// the parser read `}` and finished processing a JSON object object_end, /// the parser read `[` and started to process a JSON array array_start, /// the parser read `]` and finished processing a JSON array array_end, /// the parser read a key of a value in an object key, /// the parser finished reading a JSON value value }; template using parser_callback_t = std::function; /*! @brief syntax analysis This class implements a recursive descent parser. */ template class parser { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using lexer_t = lexer; using token_type = typename lexer_t::token_type; public: /// a parser reading from an input adapter explicit parser(InputAdapterType&& adapter, const parser_callback_t cb = nullptr, const bool allow_exceptions_ = true, const bool skip_comments = false) : callback(cb) , m_lexer(std::move(adapter), skip_comments) , allow_exceptions(allow_exceptions_) { // read first token get_token(); } /*! @brief public parser interface @param[in] strict whether to expect the last token to be EOF @param[in,out] result parsed JSON value @throw parse_error.101 in case of an unexpected token @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails */ void parse(const bool strict, BasicJsonType& result) { if (callback) { json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); sax_parse_internal(&sdp); // in strict mode, input must be completely read if (strict && (get_token() != token_type::end_of_input)) { sdp.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), BasicJsonType())); } // in case of an error, return discarded value if (sdp.is_errored()) { result = value_t::discarded; return; } // set top-level value to null if it was discarded by the callback // function if (result.is_discarded()) { result = nullptr; } } else { json_sax_dom_parser sdp(result, allow_exceptions); sax_parse_internal(&sdp); // in strict mode, input must be completely read if (strict && (get_token() != token_type::end_of_input)) { sdp.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), BasicJsonType())); } // in case of an error, return discarded value if (sdp.is_errored()) { result = value_t::discarded; return; } } result.assert_invariant(); } /*! @brief public accept interface @param[in] strict whether to expect the last token to be EOF @return whether the input is a proper JSON text */ bool accept(const bool strict = true) { json_sax_acceptor sax_acceptor; return sax_parse(&sax_acceptor, strict); } template JSON_HEDLEY_NON_NULL(2) bool sax_parse(SAX* sax, const bool strict = true) { (void)detail::is_sax_static_asserts {}; const bool result = sax_parse_internal(sax); // strict mode: next byte must be EOF if (result && strict && (get_token() != token_type::end_of_input)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), BasicJsonType())); } return result; } private: template JSON_HEDLEY_NON_NULL(2) bool sax_parse_internal(SAX* sax) { // stack to remember the hierarchy of structured values we are parsing // true = array; false = object std::vector states; // value to avoid a goto (see comment where set to true) bool skip_to_state_evaluation = false; while (true) { if (!skip_to_state_evaluation) { // invariant: get_token() was called before each iteration switch (last_token) { case token_type::begin_object: { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) { return false; } // closing } -> we are done if (get_token() == token_type::end_object) { if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) { return false; } break; } // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), BasicJsonType())); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { return false; } // parse separator (:) if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), BasicJsonType())); } // remember we are now inside an object states.push_back(false); // parse values get_token(); continue; } case token_type::begin_array: { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) { return false; } // closing ] -> we are done if (get_token() == token_type::end_array) { if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) { return false; } break; } // remember we are now inside an array states.push_back(true); // parse values (no need to call get_token) continue; } case token_type::value_float: { const auto res = m_lexer.get_number_float(); if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'", BasicJsonType())); } if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) { return false; } break; } case token_type::literal_false: { if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) { return false; } break; } case token_type::literal_null: { if (JSON_HEDLEY_UNLIKELY(!sax->null())) { return false; } break; } case token_type::literal_true: { if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) { return false; } break; } case token_type::value_integer: { if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) { return false; } break; } case token_type::value_string: { if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) { return false; } break; } case token_type::value_unsigned: { if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned()))) { return false; } break; } case token_type::parse_error: { // using "uninitialized" to avoid "expected" message return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), BasicJsonType())); } case token_type::uninitialized: case token_type::end_array: case token_type::end_object: case token_type::name_separator: case token_type::value_separator: case token_type::end_of_input: case token_type::literal_or_value: default: // the last token was unexpected { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), BasicJsonType())); } } } else { skip_to_state_evaluation = false; } // we reached this line after we successfully parsed a value if (states.empty()) { // empty stack: we reached the end of the hierarchy: done return true; } if (states.back()) // array { // comma -> next value if (get_token() == token_type::value_separator) { // parse a new value get_token(); continue; } // closing ] if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) { if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) { return false; } // We are done with this array. Before we can parse a // new value, we need to evaluate the new state first. // By setting skip_to_state_evaluation to false, we // are effectively jumping to the beginning of this if. JSON_ASSERT(!states.empty()); states.pop_back(); skip_to_state_evaluation = true; continue; } return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), BasicJsonType())); } // states.back() is false -> object // comma -> next value if (get_token() == token_type::value_separator) { // parse key if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), BasicJsonType())); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { return false; } // parse separator (:) if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), BasicJsonType())); } // parse values get_token(); continue; } // closing } if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) { if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) { return false; } // We are done with this object. Before we can parse a // new value, we need to evaluate the new state first. // By setting skip_to_state_evaluation to false, we // are effectively jumping to the beginning of this if. JSON_ASSERT(!states.empty()); states.pop_back(); skip_to_state_evaluation = true; continue; } return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), BasicJsonType())); } } /// get next token from lexer token_type get_token() { return last_token = m_lexer.scan(); } std::string exception_message(const token_type expected, const std::string& context) { std::string error_msg = "syntax error "; if (!context.empty()) { error_msg += "while parsing " + context + " "; } error_msg += "- "; if (last_token == token_type::parse_error) { error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + m_lexer.get_token_string() + "'"; } else { error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); } if (expected != token_type::uninitialized) { error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); } return error_msg; } private: /// callback function const parser_callback_t callback = nullptr; /// the type of the last read token token_type last_token = token_type::uninitialized; /// the lexer lexer_t m_lexer; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; } // namespace detail } // namespace nlohmann // #include // #include #include // ptrdiff_t #include // numeric_limits // #include namespace nlohmann { namespace detail { /* @brief an iterator for primitive JSON types This class models an iterator for primitive JSON types (boolean, number, string). It's only purpose is to allow the iterator/const_iterator classes to "iterate" over primitive values. Internally, the iterator is modeled by a `difference_type` variable. Value begin_value (`0`) models the begin, end_value (`1`) models past the end. */ class primitive_iterator_t { private: using difference_type = std::ptrdiff_t; static constexpr difference_type begin_value = 0; static constexpr difference_type end_value = begin_value + 1; JSON_PRIVATE_UNLESS_TESTED: /// iterator as signed integer type difference_type m_it = (std::numeric_limits::min)(); public: constexpr difference_type get_value() const noexcept { return m_it; } /// set iterator to a defined beginning void set_begin() noexcept { m_it = begin_value; } /// set iterator to a defined past the end void set_end() noexcept { m_it = end_value; } /// return whether the iterator can be dereferenced constexpr bool is_begin() const noexcept { return m_it == begin_value; } /// return whether the iterator is at end constexpr bool is_end() const noexcept { return m_it == end_value; } friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it == rhs.m_it; } friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it < rhs.m_it; } primitive_iterator_t operator+(difference_type n) noexcept { auto result = *this; result += n; return result; } friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it - rhs.m_it; } primitive_iterator_t& operator++() noexcept { ++m_it; return *this; } primitive_iterator_t const operator++(int) noexcept // NOLINT(readability-const-return-type) { auto result = *this; ++m_it; return result; } primitive_iterator_t& operator--() noexcept { --m_it; return *this; } primitive_iterator_t const operator--(int) noexcept // NOLINT(readability-const-return-type) { auto result = *this; --m_it; return result; } primitive_iterator_t& operator+=(difference_type n) noexcept { m_it += n; return *this; } primitive_iterator_t& operator-=(difference_type n) noexcept { m_it -= n; return *this; } }; } // namespace detail } // namespace nlohmann namespace nlohmann { namespace detail { /*! @brief an iterator value @note This structure could easily be a union, but MSVC currently does not allow unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. */ template struct internal_iterator { /// iterator for JSON objects typename BasicJsonType::object_t::iterator object_iterator {}; /// iterator for JSON arrays typename BasicJsonType::array_t::iterator array_iterator {}; /// generic iterator for all other types primitive_iterator_t primitive_iterator {}; }; } // namespace detail } // namespace nlohmann // #include #include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next #include // conditional, is_const, remove_const // #include // #include // #include // #include // #include // #include // #include namespace nlohmann { namespace detail { // forward declare, to be able to friend it later on template class iteration_proxy; template class iteration_proxy_value; /*! @brief a template for a bidirectional iterator for the @ref basic_json class This class implements a both iterators (iterator and const_iterator) for the @ref basic_json class. @note An iterator is called *initialized* when a pointer to a JSON value has been set (e.g., by a constructor or a copy assignment). If the iterator is default-constructed, it is *uninitialized* and most methods are undefined. **The library uses assertions to detect calls on uninitialized iterators.** @requirement The class satisfies the following concept requirements: - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. incremented and decremented). @since version 1.0.0, simplified in version 2.0.9, change to bidirectional iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) */ template class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) { /// the iterator with BasicJsonType of different const-ness using other_iter_impl = iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; /// allow basic_json to access private members friend other_iter_impl; friend BasicJsonType; friend iteration_proxy; friend iteration_proxy_value; using object_t = typename BasicJsonType::object_t; using array_t = typename BasicJsonType::array_t; // make sure BasicJsonType is basic_json or const basic_json static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); public: /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. /// The C++ Standard has never required user-defined iterators to derive from std::iterator. /// A user-defined iterator should provide publicly accessible typedefs named /// iterator_category, value_type, difference_type, pointer, and reference. /// Note that value_type is required to be non-const, even for constant iterators. using iterator_category = std::bidirectional_iterator_tag; /// the type of the values when the iterator is dereferenced using value_type = typename BasicJsonType::value_type; /// a type to represent differences between iterators using difference_type = typename BasicJsonType::difference_type; /// defines a pointer to the type iterated over (value_type) using pointer = typename std::conditional::value, typename BasicJsonType::const_pointer, typename BasicJsonType::pointer>::type; /// defines a reference to the type iterated over (value_type) using reference = typename std::conditional::value, typename BasicJsonType::const_reference, typename BasicJsonType::reference>::type; iter_impl() = default; ~iter_impl() = default; iter_impl(iter_impl&&) noexcept = default; iter_impl& operator=(iter_impl&&) noexcept = default; /*! @brief constructor for a given JSON instance @param[in] object pointer to a JSON object for this iterator @pre object != nullptr @post The iterator is initialized; i.e. `m_object != nullptr`. */ explicit iter_impl(pointer object) noexcept : m_object(object) { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = typename object_t::iterator(); break; } case value_t::array: { m_it.array_iterator = typename array_t::iterator(); break; } case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { m_it.primitive_iterator = primitive_iterator_t(); break; } } } /*! @note The conventional copy constructor and copy assignment are implicitly defined. Combined with the following converting constructor and assignment, they support: (1) copy from iterator to iterator, (2) copy from const iterator to const iterator, and (3) conversion from iterator to const iterator. However conversion from const iterator to iterator is not defined. */ /*! @brief const copy constructor @param[in] other const iterator to copy from @note This copy constructor had to be defined explicitly to circumvent a bug occurring on msvc v19.0 compiler (VS 2015) debug build. For more information refer to: https://github.com/nlohmann/json/issues/1608 */ iter_impl(const iter_impl& other) noexcept : m_object(other.m_object), m_it(other.m_it) {} /*! @brief converting assignment @param[in] other const iterator to copy from @return const/non-const iterator @note It is not checked whether @a other is initialized. */ iter_impl& operator=(const iter_impl& other) noexcept { if (&other != this) { m_object = other.m_object; m_it = other.m_it; } return *this; } /*! @brief converting constructor @param[in] other non-const iterator to copy from @note It is not checked whether @a other is initialized. */ iter_impl(const iter_impl::type>& other) noexcept : m_object(other.m_object), m_it(other.m_it) {} /*! @brief converting assignment @param[in] other non-const iterator to copy from @return const/non-const iterator @note It is not checked whether @a other is initialized. */ iter_impl& operator=(const iter_impl::type>& other) noexcept // NOLINT(cert-oop54-cpp) { m_object = other.m_object; m_it = other.m_it; return *this; } JSON_PRIVATE_UNLESS_TESTED: /*! @brief set the iterator to the first value @pre The iterator is initialized; i.e. `m_object != nullptr`. */ void set_begin() noexcept { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = m_object->m_value.object->begin(); break; } case value_t::array: { m_it.array_iterator = m_object->m_value.array->begin(); break; } case value_t::null: { // set to end so begin()==end() is true: null is empty m_it.primitive_iterator.set_end(); break; } case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { m_it.primitive_iterator.set_begin(); break; } } } /*! @brief set the iterator past the last value @pre The iterator is initialized; i.e. `m_object != nullptr`. */ void set_end() noexcept { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = m_object->m_value.object->end(); break; } case value_t::array: { m_it.array_iterator = m_object->m_value.array->end(); break; } case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { m_it.primitive_iterator.set_end(); break; } } } public: /*! @brief return a reference to the value pointed to by the iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference operator*() const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); return m_it.object_iterator->second; } case value_t::array: { JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); return *m_it.array_iterator; } case value_t::null: JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object)); case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) { return *m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object)); } } } /*! @brief dereference the iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ pointer operator->() const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); return &(m_it.object_iterator->second); } case value_t::array: { JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); return &*m_it.array_iterator; } case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) { return m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object)); } } } /*! @brief post-increment (it++) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl const operator++(int) // NOLINT(readability-const-return-type) { auto result = *this; ++(*this); return result; } /*! @brief pre-increment (++it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator++() { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { std::advance(m_it.object_iterator, 1); break; } case value_t::array: { std::advance(m_it.array_iterator, 1); break; } case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { ++m_it.primitive_iterator; break; } } return *this; } /*! @brief post-decrement (it--) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl const operator--(int) // NOLINT(readability-const-return-type) { auto result = *this; --(*this); return result; } /*! @brief pre-decrement (--it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator--() { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { std::advance(m_it.object_iterator, -1); break; } case value_t::array: { std::advance(m_it.array_iterator, -1); break; } case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { --m_it.primitive_iterator; break; } } return *this; } /*! @brief comparison: equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator==(const IterImpl& other) const { // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", *m_object)); } JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: return (m_it.object_iterator == other.m_it.object_iterator); case value_t::array: return (m_it.array_iterator == other.m_it.array_iterator); case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: return (m_it.primitive_iterator == other.m_it.primitive_iterator); } } /*! @brief comparison: not equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator!=(const IterImpl& other) const { return !operator==(other); } /*! @brief comparison: smaller @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator<(const iter_impl& other) const { // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", *m_object)); } JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", *m_object)); case value_t::array: return (m_it.array_iterator < other.m_it.array_iterator); case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: return (m_it.primitive_iterator < other.m_it.primitive_iterator); } } /*! @brief comparison: less than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator<=(const iter_impl& other) const { return !other.operator < (*this); } /*! @brief comparison: greater than @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator>(const iter_impl& other) const { return !operator<=(other); } /*! @brief comparison: greater than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator>=(const iter_impl& other) const { return !operator<(other); } /*! @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator+=(difference_type i) { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", *m_object)); case value_t::array: { std::advance(m_it.array_iterator, i); break; } case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { m_it.primitive_iterator += i; break; } } return *this; } /*! @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator-=(difference_type i) { return operator+=(-i); } /*! @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl operator+(difference_type i) const { auto result = *this; result += i; return result; } /*! @brief addition of distance and iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ friend iter_impl operator+(difference_type i, const iter_impl& it) { auto result = it; result += i; return result; } /*! @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl operator-(difference_type i) const { auto result = *this; result -= i; return result; } /*! @brief return difference @pre The iterator is initialized; i.e. `m_object != nullptr`. */ difference_type operator-(const iter_impl& other) const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", *m_object)); case value_t::array: return m_it.array_iterator - other.m_it.array_iterator; case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: return m_it.primitive_iterator - other.m_it.primitive_iterator; } } /*! @brief access to successor @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference operator[](difference_type n) const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", *m_object)); case value_t::array: return *std::next(m_it.array_iterator, n); case value_t::null: JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object)); case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) { return *m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object)); } } } /*! @brief return the key of an object iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ const typename object_t::key_type& key() const { JSON_ASSERT(m_object != nullptr); if (JSON_HEDLEY_LIKELY(m_object->is_object())) { return m_it.object_iterator->first; } JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", *m_object)); } /*! @brief return the value of an iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference value() const { return operator*(); } JSON_PRIVATE_UNLESS_TESTED: /// associated JSON instance pointer m_object = nullptr; /// the actual iterator of the associated instance internal_iterator::type> m_it {}; }; } // namespace detail } // namespace nlohmann // #include // #include #include // ptrdiff_t #include // reverse_iterator #include // declval namespace nlohmann { namespace detail { ////////////////////// // reverse_iterator // ////////////////////// /*! @brief a template for a reverse iterator class @tparam Base the base iterator type to reverse. Valid types are @ref iterator (to create @ref reverse_iterator) and @ref const_iterator (to create @ref const_reverse_iterator). @requirement The class satisfies the following concept requirements: - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. incremented and decremented). - [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): It is possible to write to the pointed-to element (only if @a Base is @ref iterator). @since version 1.0.0 */ template class json_reverse_iterator : public std::reverse_iterator { public: using difference_type = std::ptrdiff_t; /// shortcut to the reverse iterator adapter using base_iterator = std::reverse_iterator; /// the reference type for the pointed-to element using reference = typename Base::reference; /// create reverse iterator from iterator explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept : base_iterator(it) {} /// create reverse iterator from base class explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} /// post-increment (it++) json_reverse_iterator const operator++(int) // NOLINT(readability-const-return-type) { return static_cast(base_iterator::operator++(1)); } /// pre-increment (++it) json_reverse_iterator& operator++() { return static_cast(base_iterator::operator++()); } /// post-decrement (it--) json_reverse_iterator const operator--(int) // NOLINT(readability-const-return-type) { return static_cast(base_iterator::operator--(1)); } /// pre-decrement (--it) json_reverse_iterator& operator--() { return static_cast(base_iterator::operator--()); } /// add to iterator json_reverse_iterator& operator+=(difference_type i) { return static_cast(base_iterator::operator+=(i)); } /// add to iterator json_reverse_iterator operator+(difference_type i) const { return static_cast(base_iterator::operator+(i)); } /// subtract from iterator json_reverse_iterator operator-(difference_type i) const { return static_cast(base_iterator::operator-(i)); } /// return difference difference_type operator-(const json_reverse_iterator& other) const { return base_iterator(*this) - base_iterator(other); } /// access to successor reference operator[](difference_type n) const { return *(this->operator+(n)); } /// return the key of an object iterator auto key() const -> decltype(std::declval().key()) { auto it = --this->base(); return it.key(); } /// return the value of an iterator reference value() const { auto it = --this->base(); return it.operator * (); } }; } // namespace detail } // namespace nlohmann // #include // #include #include // all_of #include // isdigit #include // max #include // accumulate #include // string #include // move #include // vector // #include // #include // #include // #include namespace nlohmann { /// @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 { // allow basic_json to access private members NLOHMANN_BASIC_JSON_TPL_DECLARATION friend class basic_json; public: /// @brief create JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/ explicit json_pointer(const std::string& s = "") : reference_tokens(split(s)) {} /// @brief return a string representation of the JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/to_string/ std::string to_string() const { return std::accumulate(reference_tokens.begin(), reference_tokens.end(), std::string{}, [](const std::string & a, const std::string & b) { return a + "/" + detail::escape(b); }); } /// @brief return a string representation of the JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_string/ operator std::string() const { return to_string(); } /// @brief append another JSON pointer at the end of this JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ json_pointer& operator/=(const json_pointer& ptr) { reference_tokens.insert(reference_tokens.end(), ptr.reference_tokens.begin(), ptr.reference_tokens.end()); return *this; } /// @brief append an unescaped reference token at the end of this JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ json_pointer& operator/=(std::string token) { push_back(std::move(token)); return *this; } /// @brief append an array index at the end of this JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ json_pointer& operator/=(std::size_t array_idx) { return *this /= std::to_string(array_idx); } /// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ friend json_pointer operator/(const json_pointer& lhs, const json_pointer& rhs) { return json_pointer(lhs) /= rhs; } /// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ friend json_pointer operator/(const json_pointer& lhs, std::string token) // NOLINT(performance-unnecessary-value-param) { return json_pointer(lhs) /= std::move(token); } /// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx) { return json_pointer(lhs) /= array_idx; } /// @brief returns the parent of this JSON pointer /// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/ json_pointer parent_pointer() const { if (empty()) { return *this; } json_pointer res = *this; res.pop_back(); return res; } /// @brief remove last reference token /// @sa https://json.nlohmann.me/api/json_pointer/pop_back/ void pop_back() { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType())); } reference_tokens.pop_back(); } /// @brief return last reference token /// @sa https://json.nlohmann.me/api/json_pointer/back/ const std::string& back() const { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType())); } return reference_tokens.back(); } /// @brief append an unescaped token at the end of the reference pointer /// @sa https://json.nlohmann.me/api/json_pointer/push_back/ void push_back(const std::string& token) { reference_tokens.push_back(token); } /// @brief append an unescaped token at the end of the reference pointer /// @sa https://json.nlohmann.me/api/json_pointer/push_back/ void push_back(std::string&& token) { reference_tokens.push_back(std::move(token)); } /// @brief return whether pointer points to the root document /// @sa https://json.nlohmann.me/api/json_pointer/empty/ bool empty() const noexcept { return reference_tokens.empty(); } private: /*! @param[in] s reference token to be converted into an array index @return integer representation of @a s @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index begins not with a digit @throw out_of_range.404 if string @a s could not be converted to an integer @throw out_of_range.410 if an array index exceeds size_type */ static typename BasicJsonType::size_type array_index(const std::string& s) { using size_type = typename BasicJsonType::size_type; // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) { JSON_THROW(detail::parse_error::create(106, 0, "array index '" + s + "' must not begin with '0'", BasicJsonType())); } // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) { JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number", BasicJsonType())); } std::size_t processed_chars = 0; unsigned long long res = 0; // NOLINT(runtime/int) JSON_TRY { res = std::stoull(s, &processed_chars); } JSON_CATCH(std::out_of_range&) { JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'", BasicJsonType())); } // check if the string was completely read if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) { JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'", BasicJsonType())); } // only triggered on special platforms (like 32bit), see also // https://github.com/nlohmann/json/pull/2203 if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) { JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type", BasicJsonType())); // LCOV_EXCL_LINE } return static_cast(res); } JSON_PRIVATE_UNLESS_TESTED: json_pointer top() const { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType())); } json_pointer result = *this; result.reference_tokens = {reference_tokens[0]}; return result; } private: /*! @brief create and return a reference to the pointed to value @complexity Linear in the number of reference tokens. @throw parse_error.109 if array index is not a number @throw type_error.313 if value cannot be unflattened */ BasicJsonType& get_and_create(BasicJsonType& j) const { auto* result = &j; // in case no reference tokens exist, return a reference to the JSON value // j which will be overwritten by a primitive value for (const auto& reference_token : reference_tokens) { switch (result->type()) { case detail::value_t::null: { if (reference_token == "0") { // start a new array if reference token is 0 result = &result->operator[](0); } else { // start a new object otherwise result = &result->operator[](reference_token); } break; } case detail::value_t::object: { // create an entry in the object result = &result->operator[](reference_token); break; } case detail::value_t::array: { // create an entry in the array result = &result->operator[](array_index(reference_token)); break; } /* The following code is only reached if there exists a reference token _and_ the current value is primitive. In this case, we have an error situation, because primitive values may only occur as single value; that is, with an empty list of reference tokens. */ case detail::value_t::string: case detail::value_t::boolean: case detail::value_t::number_integer: case detail::value_t::number_unsigned: case detail::value_t::number_float: case detail::value_t::binary: case detail::value_t::discarded: default: JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", j)); } } return *result; } /*! @brief return a reference to the pointed to value @note This version does not throw if a value is not present, but tries to create nested values instead. For instance, calling this function with pointer `"/this/that"` on a null value is equivalent to calling `operator[]("this").operator[]("that")` on that value, effectively changing the null value to an object. @param[in] ptr a JSON value @return reference to the JSON value pointed to by the JSON pointer @complexity Linear in the length of the JSON pointer. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.404 if the JSON pointer can not be resolved */ BasicJsonType& get_unchecked(BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { // convert null values to arrays or objects before continuing if (ptr->is_null()) { // check if reference token is a number const bool nums = std::all_of(reference_token.begin(), reference_token.end(), [](const unsigned char x) { return std::isdigit(x); }); // change value to array for numbers or "-" or to object otherwise *ptr = (nums || reference_token == "-") ? detail::value_t::array : detail::value_t::object; } switch (ptr->type()) { case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (reference_token == "-") { // explicitly treat "-" as index beyond the end ptr = &ptr->operator[](ptr->m_value.array->size()); } else { // convert array index to number; unchecked access ptr = &ptr->operator[](array_index(reference_token)); } break; } case detail::value_t::null: case detail::value_t::string: case detail::value_t::boolean: case detail::value_t::number_integer: case detail::value_t::number_unsigned: case detail::value_t::number_float: case detail::value_t::binary: case detail::value_t::discarded: default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ BasicJsonType& get_checked(BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range", *ptr)); } // note: at performs range check ptr = &ptr->at(array_index(reference_token)); break; } case detail::value_t::null: case detail::value_t::string: case detail::value_t::boolean: case detail::value_t::number_integer: case detail::value_t::number_unsigned: case detail::value_t::number_float: case detail::value_t::binary: case detail::value_t::discarded: default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); } } return *ptr; } /*! @brief return a const reference to the pointed to value @param[in] ptr a JSON value @return const reference to the JSON value pointed to by the JSON pointer @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" cannot be used for const access JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range", *ptr)); } // use unchecked array access ptr = &ptr->operator[](array_index(reference_token)); break; } case detail::value_t::null: case detail::value_t::string: case detail::value_t::boolean: case detail::value_t::number_integer: case detail::value_t::number_unsigned: case detail::value_t::number_float: case detail::value_t::binary: case detail::value_t::discarded: default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ const BasicJsonType& get_checked(const BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range", *ptr)); } // note: at performs range check ptr = &ptr->at(array_index(reference_token)); break; } case detail::value_t::null: case detail::value_t::string: case detail::value_t::boolean: case detail::value_t::number_integer: case detail::value_t::number_unsigned: case detail::value_t::number_float: case detail::value_t::binary: case detail::value_t::discarded: default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number */ bool contains(const BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { if (!ptr->contains(reference_token)) { // we did not find the key in the object return false; } ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check return false; } if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) { // invalid char return false; } if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) { if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) { // first char should be between '1' and '9' return false; } for (std::size_t i = 1; i < reference_token.size(); i++) { if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) { // other char should be between '0' and '9' return false; } } } const auto idx = array_index(reference_token); if (idx >= ptr->size()) { // index out of range return false; } ptr = &ptr->operator[](idx); break; } case detail::value_t::null: case detail::value_t::string: case detail::value_t::boolean: case detail::value_t::number_integer: case detail::value_t::number_unsigned: case detail::value_t::number_float: case detail::value_t::binary: case detail::value_t::discarded: default: { // we do not expect primitive values if there is still a // reference token to process return false; } } } // no reference token left means we found a primitive value return true; } /*! @brief split the string input to reference tokens @note This function is only called by the json_pointer constructor. All exceptions below are documented there. @throw parse_error.107 if the pointer is not empty or begins with '/' @throw parse_error.108 if character '~' is not followed by '0' or '1' */ static std::vector split(const std::string& reference_string) { std::vector result; // special case: empty reference string -> no reference tokens if (reference_string.empty()) { return result; } // check if nonempty reference string begins with slash if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) { JSON_THROW(detail::parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'", BasicJsonType())); } // extract the reference tokens: // - slash: position of the last read slash (or end of string) // - start: position after the previous slash for ( // search for the first slash after the first character std::size_t slash = reference_string.find_first_of('/', 1), // set the beginning of the first reference token start = 1; // we can stop if start == 0 (if slash == std::string::npos) start != 0; // set the beginning of the next reference token // (will eventually be 0 if slash == std::string::npos) start = (slash == std::string::npos) ? 0 : slash + 1, // find next slash slash = reference_string.find_first_of('/', start)) { // use the text between the beginning of the reference token // (start) and the last slash (slash). auto reference_token = reference_string.substr(start, slash - start); // check reference tokens are properly escaped for (std::size_t pos = reference_token.find_first_of('~'); pos != std::string::npos; pos = reference_token.find_first_of('~', pos + 1)) { JSON_ASSERT(reference_token[pos] == '~'); // ~ must be followed by 0 or 1 if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || (reference_token[pos + 1] != '0' && reference_token[pos + 1] != '1'))) { JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", BasicJsonType())); } } // finally, store the reference token detail::unescape(reference_token); result.push_back(reference_token); } return result; } private: /*! @param[in] reference_string the reference string to the current value @param[in] value the value to consider @param[in,out] result the result object to insert values to @note Empty objects or arrays are flattened to `null`. */ static void flatten(const std::string& reference_string, const BasicJsonType& value, BasicJsonType& result) { switch (value.type()) { case detail::value_t::array: { if (value.m_value.array->empty()) { // flatten empty array as null result[reference_string] = nullptr; } else { // iterate array and use index as reference string for (std::size_t i = 0; i < value.m_value.array->size(); ++i) { flatten(reference_string + "/" + std::to_string(i), value.m_value.array->operator[](i), result); } } break; } case detail::value_t::object: { if (value.m_value.object->empty()) { // flatten empty object as null result[reference_string] = nullptr; } else { // iterate object and use keys as reference string for (const auto& element : *value.m_value.object) { flatten(reference_string + "/" + detail::escape(element.first), element.second, result); } } break; } case detail::value_t::null: case detail::value_t::string: case detail::value_t::boolean: case detail::value_t::number_integer: case detail::value_t::number_unsigned: case detail::value_t::number_float: case detail::value_t::binary: case detail::value_t::discarded: default: { // add primitive value with its reference string result[reference_string] = value; break; } } } /*! @param[in] value flattened JSON @return unflattened JSON @throw parse_error.109 if array index is not a number @throw type_error.314 if value is not an object @throw type_error.315 if object values are not primitive @throw type_error.313 if value cannot be unflattened */ static BasicJsonType unflatten(const BasicJsonType& value) { if (JSON_HEDLEY_UNLIKELY(!value.is_object())) { JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", value)); } BasicJsonType result; // iterate the JSON object values for (const auto& element : *value.m_value.object) { if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) { JSON_THROW(detail::type_error::create(315, "values in object must be primitive", element.second)); } // assign value to reference pointed to by JSON pointer; Note that if // the JSON pointer is "" (i.e., points to the whole value), function // get_and_create returns a reference to result itself. An assignment // will then create a primitive value. json_pointer(element.first).get_and_create(result) = element.second; } return result; } /*! @brief compares two JSON pointers for equality @param[in] lhs JSON pointer to compare @param[in] rhs JSON pointer to compare @return whether @a lhs is equal to @a rhs @complexity Linear in the length of the JSON pointer @exceptionsafety No-throw guarantee: this function never throws exceptions. */ friend bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept { return lhs.reference_tokens == rhs.reference_tokens; } /*! @brief compares two JSON pointers for inequality @param[in] lhs JSON pointer to compare @param[in] rhs JSON pointer to compare @return whether @a lhs is not equal @a rhs @complexity Linear in the length of the JSON pointer @exceptionsafety No-throw guarantee: this function never throws exceptions. */ friend bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept { return !(lhs == rhs); } /// the reference tokens std::vector reference_tokens; }; } // namespace nlohmann // #include #include #include // #include namespace nlohmann { namespace detail { template class json_ref { public: using value_type = BasicJsonType; json_ref(value_type&& value) : owned_value(std::move(value)) {} json_ref(const value_type& value) : value_ref(&value) {} json_ref(std::initializer_list init) : owned_value(init) {} template < class... Args, enable_if_t::value, int> = 0 > json_ref(Args && ... args) : owned_value(std::forward(args)...) {} // class should be movable only json_ref(json_ref&&) noexcept = default; json_ref(const json_ref&) = delete; json_ref& operator=(const json_ref&) = delete; json_ref& operator=(json_ref&&) = delete; ~json_ref() = default; value_type moved_or_copied() const { if (value_ref == nullptr) { return std::move(owned_value); } return *value_ref; } value_type const& operator*() const { return value_ref ? *value_ref : owned_value; } value_type const* operator->() const { return &** this; } private: mutable value_type owned_value = nullptr; value_type const* value_ref = nullptr; }; } // namespace detail } // namespace nlohmann // #include // #include // #include // #include // #include #include // reverse #include // array #include // isnan, isinf #include // uint8_t, uint16_t, uint32_t, uint64_t #include // memcpy #include // numeric_limits #include // string #include // move // #include // #include // #include #include // copy #include // size_t #include // back_inserter #include // shared_ptr, make_shared #include // basic_string #include // vector #ifndef JSON_NO_IO #include // streamsize #include // basic_ostream #endif // JSON_NO_IO // #include namespace nlohmann { namespace detail { /// abstract output adapter interface template struct output_adapter_protocol { virtual void write_character(CharType c) = 0; virtual void write_characters(const CharType* s, std::size_t length) = 0; virtual ~output_adapter_protocol() = default; output_adapter_protocol() = default; output_adapter_protocol(const output_adapter_protocol&) = default; output_adapter_protocol(output_adapter_protocol&&) noexcept = default; output_adapter_protocol& operator=(const output_adapter_protocol&) = default; output_adapter_protocol& operator=(output_adapter_protocol&&) noexcept = default; }; /// a type to simplify interfaces template using output_adapter_t = std::shared_ptr>; /// output adapter for byte vectors template> class output_vector_adapter : public output_adapter_protocol { public: explicit output_vector_adapter(std::vector& vec) noexcept : v(vec) {} void write_character(CharType c) override { v.push_back(c); } JSON_HEDLEY_NON_NULL(2) void write_characters(const CharType* s, std::size_t length) override { std::copy(s, s + length, std::back_inserter(v)); } private: std::vector& v; }; #ifndef JSON_NO_IO /// output adapter for output streams template class output_stream_adapter : public output_adapter_protocol { public: explicit output_stream_adapter(std::basic_ostream& s) noexcept : stream(s) {} void write_character(CharType c) override { stream.put(c); } JSON_HEDLEY_NON_NULL(2) void write_characters(const CharType* s, std::size_t length) override { stream.write(s, static_cast(length)); } private: std::basic_ostream& stream; }; #endif // JSON_NO_IO /// output adapter for basic_string template> class output_string_adapter : public output_adapter_protocol { public: explicit output_string_adapter(StringType& s) noexcept : str(s) {} void write_character(CharType c) override { str.push_back(c); } JSON_HEDLEY_NON_NULL(2) void write_characters(const CharType* s, std::size_t length) override { str.append(s, length); } private: StringType& str; }; template> class output_adapter { public: template> output_adapter(std::vector& vec) : oa(std::make_shared>(vec)) {} #ifndef JSON_NO_IO output_adapter(std::basic_ostream& s) : oa(std::make_shared>(s)) {} #endif // JSON_NO_IO output_adapter(StringType& s) : oa(std::make_shared>(s)) {} operator output_adapter_t() { return oa; } private: output_adapter_t oa = nullptr; }; } // namespace detail } // namespace nlohmann namespace nlohmann { namespace detail { /////////////////// // binary writer // /////////////////// /*! @brief serialization to CBOR and MessagePack values */ template class binary_writer { using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using number_float_t = typename BasicJsonType::number_float_t; public: /*! @brief create a binary writer @param[in] adapter output adapter to write to */ explicit binary_writer(output_adapter_t adapter) : oa(std::move(adapter)) { JSON_ASSERT(oa); } /*! @param[in] j JSON value to serialize @pre j.type() == value_t::object */ void write_bson(const BasicJsonType& j) { switch (j.type()) { case value_t::object: { write_bson_object(*j.m_value.object); break; } case value_t::null: case value_t::array: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()), j)); } } } /*! @param[in] j JSON value to serialize */ void write_cbor(const BasicJsonType& j) { switch (j.type()) { case value_t::null: { oa->write_character(to_char_type(0xF6)); break; } case value_t::boolean: { oa->write_character(j.m_value.boolean ? to_char_type(0xF5) : to_char_type(0xF4)); break; } case value_t::number_integer: { if (j.m_value.number_integer >= 0) { // CBOR does not differentiate between positive signed // integers and unsigned integers. Therefore, we used the // code from the value_t::number_unsigned case here. if (j.m_value.number_integer <= 0x17) { write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x18)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x19)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x1A)); write_number(static_cast(j.m_value.number_integer)); } else { oa->write_character(to_char_type(0x1B)); write_number(static_cast(j.m_value.number_integer)); } } else { // The conversions below encode the sign in the first // byte, and the value is converted to a positive number. const auto positive_number = -1 - j.m_value.number_integer; if (j.m_value.number_integer >= -24) { write_number(static_cast(0x20 + positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x38)); write_number(static_cast(positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x39)); write_number(static_cast(positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x3A)); write_number(static_cast(positive_number)); } else { oa->write_character(to_char_type(0x3B)); write_number(static_cast(positive_number)); } } break; } case value_t::number_unsigned: { if (j.m_value.number_unsigned <= 0x17) { write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x18)); write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x19)); write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x1A)); write_number(static_cast(j.m_value.number_unsigned)); } else { oa->write_character(to_char_type(0x1B)); write_number(static_cast(j.m_value.number_unsigned)); } break; } case value_t::number_float: { if (std::isnan(j.m_value.number_float)) { // NaN is 0xf97e00 in CBOR oa->write_character(to_char_type(0xF9)); oa->write_character(to_char_type(0x7E)); oa->write_character(to_char_type(0x00)); } else if (std::isinf(j.m_value.number_float)) { // Infinity is 0xf97c00, -Infinity is 0xf9fc00 oa->write_character(to_char_type(0xf9)); oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); oa->write_character(to_char_type(0x00)); } else { write_compact_float(j.m_value.number_float, detail::input_format_t::cbor); } break; } case value_t::string: { // step 1: write control byte and the string length const auto N = j.m_value.string->size(); if (N <= 0x17) { write_number(static_cast(0x60 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x78)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x79)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x7A)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x7B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write the string oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); break; } case value_t::array: { // step 1: write control byte and the array size const auto N = j.m_value.array->size(); if (N <= 0x17) { write_number(static_cast(0x80 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x98)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x99)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x9A)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x9B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write each element for (const auto& el : *j.m_value.array) { write_cbor(el); } break; } case value_t::binary: { if (j.m_value.binary->has_subtype()) { if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) { write_number(static_cast(0xd8)); write_number(static_cast(j.m_value.binary->subtype())); } else if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) { write_number(static_cast(0xd9)); write_number(static_cast(j.m_value.binary->subtype())); } else if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) { write_number(static_cast(0xda)); write_number(static_cast(j.m_value.binary->subtype())); } else if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) { write_number(static_cast(0xdb)); write_number(static_cast(j.m_value.binary->subtype())); } } // step 1: write control byte and the binary array size const auto N = j.m_value.binary->size(); if (N <= 0x17) { write_number(static_cast(0x40 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x58)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x59)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x5A)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x5B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write each element oa->write_characters( reinterpret_cast(j.m_value.binary->data()), N); break; } case value_t::object: { // step 1: write control byte and the object size const auto N = j.m_value.object->size(); if (N <= 0x17) { write_number(static_cast(0xA0 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xB8)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xB9)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xBA)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xBB)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write each element for (const auto& el : *j.m_value.object) { write_cbor(el.first); write_cbor(el.second); } break; } case value_t::discarded: default: break; } } /*! @param[in] j JSON value to serialize */ void write_msgpack(const BasicJsonType& j) { switch (j.type()) { case value_t::null: // nil { oa->write_character(to_char_type(0xC0)); break; } case value_t::boolean: // true and false { oa->write_character(j.m_value.boolean ? to_char_type(0xC3) : to_char_type(0xC2)); break; } case value_t::number_integer: { if (j.m_value.number_integer >= 0) { // MessagePack does not differentiate between positive // signed integers and unsigned integers. Therefore, we used // the code from the value_t::number_unsigned case here. if (j.m_value.number_unsigned < 128) { // positive fixnum write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 oa->write_character(to_char_type(0xCC)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 oa->write_character(to_char_type(0xCD)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 oa->write_character(to_char_type(0xCE)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 oa->write_character(to_char_type(0xCF)); write_number(static_cast(j.m_value.number_integer)); } } else { if (j.m_value.number_integer >= -32) { // negative fixnum write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 8 oa->write_character(to_char_type(0xD0)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 16 oa->write_character(to_char_type(0xD1)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 32 oa->write_character(to_char_type(0xD2)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 64 oa->write_character(to_char_type(0xD3)); write_number(static_cast(j.m_value.number_integer)); } } break; } case value_t::number_unsigned: { if (j.m_value.number_unsigned < 128) { // positive fixnum write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 oa->write_character(to_char_type(0xCC)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 oa->write_character(to_char_type(0xCD)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 oa->write_character(to_char_type(0xCE)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 oa->write_character(to_char_type(0xCF)); write_number(static_cast(j.m_value.number_integer)); } break; } case value_t::number_float: { write_compact_float(j.m_value.number_float, detail::input_format_t::msgpack); break; } case value_t::string: { // step 1: write control byte and the string length const auto N = j.m_value.string->size(); if (N <= 31) { // fixstr write_number(static_cast(0xA0 | N)); } else if (N <= (std::numeric_limits::max)()) { // str 8 oa->write_character(to_char_type(0xD9)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // str 16 oa->write_character(to_char_type(0xDA)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // str 32 oa->write_character(to_char_type(0xDB)); write_number(static_cast(N)); } // step 2: write the string oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); break; } case value_t::array: { // step 1: write control byte and the array size const auto N = j.m_value.array->size(); if (N <= 15) { // fixarray write_number(static_cast(0x90 | N)); } else if (N <= (std::numeric_limits::max)()) { // array 16 oa->write_character(to_char_type(0xDC)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // array 32 oa->write_character(to_char_type(0xDD)); write_number(static_cast(N)); } // step 2: write each element for (const auto& el : *j.m_value.array) { write_msgpack(el); } break; } case value_t::binary: { // step 0: determine if the binary type has a set subtype to // determine whether or not to use the ext or fixext types const bool use_ext = j.m_value.binary->has_subtype(); // step 1: write control byte and the byte string length const auto N = j.m_value.binary->size(); if (N <= (std::numeric_limits::max)()) { std::uint8_t output_type{}; bool fixed = true; if (use_ext) { switch (N) { case 1: output_type = 0xD4; // fixext 1 break; case 2: output_type = 0xD5; // fixext 2 break; case 4: output_type = 0xD6; // fixext 4 break; case 8: output_type = 0xD7; // fixext 8 break; case 16: output_type = 0xD8; // fixext 16 break; default: output_type = 0xC7; // ext 8 fixed = false; break; } } else { output_type = 0xC4; // bin 8 fixed = false; } oa->write_character(to_char_type(output_type)); if (!fixed) { write_number(static_cast(N)); } } else if (N <= (std::numeric_limits::max)()) { std::uint8_t output_type = use_ext ? 0xC8 // ext 16 : 0xC5; // bin 16 oa->write_character(to_char_type(output_type)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { std::uint8_t output_type = use_ext ? 0xC9 // ext 32 : 0xC6; // bin 32 oa->write_character(to_char_type(output_type)); write_number(static_cast(N)); } // step 1.5: if this is an ext type, write the subtype if (use_ext) { write_number(static_cast(j.m_value.binary->subtype())); } // step 2: write the byte string oa->write_characters( reinterpret_cast(j.m_value.binary->data()), N); break; } case value_t::object: { // step 1: write control byte and the object size const auto N = j.m_value.object->size(); if (N <= 15) { // fixmap write_number(static_cast(0x80 | (N & 0xF))); } else if (N <= (std::numeric_limits::max)()) { // map 16 oa->write_character(to_char_type(0xDE)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // map 32 oa->write_character(to_char_type(0xDF)); write_number(static_cast(N)); } // step 2: write each element for (const auto& el : *j.m_value.object) { write_msgpack(el.first); write_msgpack(el.second); } break; } case value_t::discarded: default: break; } } /*! @param[in] j JSON value to serialize @param[in] use_count whether to use '#' prefixes (optimized format) @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true) { switch (j.type()) { case value_t::null: { if (add_prefix) { oa->write_character(to_char_type('Z')); } break; } case value_t::boolean: { if (add_prefix) { oa->write_character(j.m_value.boolean ? to_char_type('T') : to_char_type('F')); } break; } case value_t::number_integer: { write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); break; } case value_t::number_unsigned: { write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); break; } case value_t::number_float: { write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); break; } case value_t::string: { if (add_prefix) { oa->write_character(to_char_type('S')); } write_number_with_ubjson_prefix(j.m_value.string->size(), true); oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); break; } case value_t::array: { if (add_prefix) { oa->write_character(to_char_type('[')); } bool prefix_required = true; if (use_type && !j.m_value.array->empty()) { JSON_ASSERT(use_count); const CharType first_prefix = ubjson_prefix(j.front()); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), [this, first_prefix](const BasicJsonType & v) { return ubjson_prefix(v) == first_prefix; }); if (same_prefix) { prefix_required = false; oa->write_character(to_char_type('$')); oa->write_character(first_prefix); } } if (use_count) { oa->write_character(to_char_type('#')); write_number_with_ubjson_prefix(j.m_value.array->size(), true); } for (const auto& el : *j.m_value.array) { write_ubjson(el, use_count, use_type, prefix_required); } if (!use_count) { oa->write_character(to_char_type(']')); } break; } case value_t::binary: { if (add_prefix) { oa->write_character(to_char_type('[')); } if (use_type && !j.m_value.binary->empty()) { JSON_ASSERT(use_count); oa->write_character(to_char_type('$')); oa->write_character('U'); } if (use_count) { oa->write_character(to_char_type('#')); write_number_with_ubjson_prefix(j.m_value.binary->size(), true); } if (use_type) { oa->write_characters( reinterpret_cast(j.m_value.binary->data()), j.m_value.binary->size()); } else { for (size_t i = 0; i < j.m_value.binary->size(); ++i) { oa->write_character(to_char_type('U')); oa->write_character(j.m_value.binary->data()[i]); } } if (!use_count) { oa->write_character(to_char_type(']')); } break; } case value_t::object: { if (add_prefix) { oa->write_character(to_char_type('{')); } bool prefix_required = true; if (use_type && !j.m_value.object->empty()) { JSON_ASSERT(use_count); const CharType first_prefix = ubjson_prefix(j.front()); const bool same_prefix = std::all_of(j.begin(), j.end(), [this, first_prefix](const BasicJsonType & v) { return ubjson_prefix(v) == first_prefix; }); if (same_prefix) { prefix_required = false; oa->write_character(to_char_type('$')); oa->write_character(first_prefix); } } if (use_count) { oa->write_character(to_char_type('#')); write_number_with_ubjson_prefix(j.m_value.object->size(), true); } for (const auto& el : *j.m_value.object) { write_number_with_ubjson_prefix(el.first.size(), true); oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); write_ubjson(el.second, use_count, use_type, prefix_required); } if (!use_count) { oa->write_character(to_char_type('}')); } break; } case value_t::discarded: default: break; } } private: ////////// // BSON // ////////// /*! @return The size of a BSON document entry header, including the id marker and the entry name size (and its null-terminator). */ static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) { const auto it = name.find(static_cast(0)); if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) { JSON_THROW(out_of_range::create(409, "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")", j)); static_cast(j); } return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; } /*! @brief Writes the given @a element_type and @a name to the output adapter */ void write_bson_entry_header(const string_t& name, const std::uint8_t element_type) { oa->write_character(to_char_type(element_type)); // boolean oa->write_characters( reinterpret_cast(name.c_str()), name.size() + 1u); } /*! @brief Writes a BSON element with key @a name and boolean value @a value */ void write_bson_boolean(const string_t& name, const bool value) { write_bson_entry_header(name, 0x08); oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00)); } /*! @brief Writes a BSON element with key @a name and double value @a value */ void write_bson_double(const string_t& name, const double value) { write_bson_entry_header(name, 0x01); write_number(value); } /*! @return The size of the BSON-encoded string in @a value */ static std::size_t calc_bson_string_size(const string_t& value) { return sizeof(std::int32_t) + value.size() + 1ul; } /*! @brief Writes a BSON element with key @a name and string value @a value */ void write_bson_string(const string_t& name, const string_t& value) { write_bson_entry_header(name, 0x02); write_number(static_cast(value.size() + 1ul)); oa->write_characters( reinterpret_cast(value.c_str()), value.size() + 1); } /*! @brief Writes a BSON element with key @a name and null value */ void write_bson_null(const string_t& name) { write_bson_entry_header(name, 0x0A); } /*! @return The size of the BSON-encoded integer @a value */ static std::size_t calc_bson_integer_size(const std::int64_t value) { return (std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)() ? sizeof(std::int32_t) : sizeof(std::int64_t); } /*! @brief Writes a BSON element with key @a name and integer @a value */ void write_bson_integer(const string_t& name, const std::int64_t value) { if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) { write_bson_entry_header(name, 0x10); // int32 write_number(static_cast(value)); } else { write_bson_entry_header(name, 0x12); // int64 write_number(static_cast(value)); } } /*! @return The size of the BSON-encoded unsigned integer in @a j */ static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept { return (value <= static_cast((std::numeric_limits::max)())) ? sizeof(std::int32_t) : sizeof(std::int64_t); } /*! @brief Writes a BSON element with key @a name and unsigned @a value */ void write_bson_unsigned(const string_t& name, const BasicJsonType& j) { if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x10 /* int32 */); write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x12 /* int64 */); write_number(static_cast(j.m_value.number_unsigned)); } else { JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(j.m_value.number_unsigned) + " cannot be represented by BSON as it does not fit int64", j)); } } /*! @brief Writes a BSON element with key @a name and object @a value */ void write_bson_object_entry(const string_t& name, const typename BasicJsonType::object_t& value) { write_bson_entry_header(name, 0x03); // object write_bson_object(value); } /*! @return The size of the BSON-encoded array @a value */ static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value) { std::size_t array_index = 0ul; const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), static_cast(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) { return result + calc_bson_element_size(std::to_string(array_index++), el); }); return sizeof(std::int32_t) + embedded_document_size + 1ul; } /*! @return The size of the BSON-encoded binary array @a value */ static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) { return sizeof(std::int32_t) + value.size() + 1ul; } /*! @brief Writes a BSON element with key @a name and array @a value */ void write_bson_array(const string_t& name, const typename BasicJsonType::array_t& value) { write_bson_entry_header(name, 0x04); // array write_number(static_cast(calc_bson_array_size(value))); std::size_t array_index = 0ul; for (const auto& el : value) { write_bson_element(std::to_string(array_index++), el); } oa->write_character(to_char_type(0x00)); } /*! @brief Writes a BSON element with key @a name and binary value @a value */ void write_bson_binary(const string_t& name, const binary_t& value) { write_bson_entry_header(name, 0x05); write_number(static_cast(value.size())); write_number(value.has_subtype() ? static_cast(value.subtype()) : static_cast(0x00)); oa->write_characters(reinterpret_cast(value.data()), value.size()); } /*! @brief Calculates the size necessary to serialize the JSON value @a j with its @a name @return The calculated size for the BSON document entry for @a j with the given @a name. */ static std::size_t calc_bson_element_size(const string_t& name, const BasicJsonType& j) { const auto header_size = calc_bson_entry_header_size(name, j); switch (j.type()) { case value_t::object: return header_size + calc_bson_object_size(*j.m_value.object); case value_t::array: return header_size + calc_bson_array_size(*j.m_value.array); case value_t::binary: return header_size + calc_bson_binary_size(*j.m_value.binary); case value_t::boolean: return header_size + 1ul; case value_t::number_float: return header_size + 8ul; case value_t::number_integer: return header_size + calc_bson_integer_size(j.m_value.number_integer); case value_t::number_unsigned: return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned); case value_t::string: return header_size + calc_bson_string_size(*j.m_value.string); case value_t::null: return header_size + 0ul; // LCOV_EXCL_START case value_t::discarded: default: JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) return 0ul; // LCOV_EXCL_STOP } } /*! @brief Serializes the JSON value @a j to BSON and associates it with the key @a name. @param name The name to associate with the JSON entity @a j within the current BSON document */ void write_bson_element(const string_t& name, const BasicJsonType& j) { switch (j.type()) { case value_t::object: return write_bson_object_entry(name, *j.m_value.object); case value_t::array: return write_bson_array(name, *j.m_value.array); case value_t::binary: return write_bson_binary(name, *j.m_value.binary); case value_t::boolean: return write_bson_boolean(name, j.m_value.boolean); case value_t::number_float: return write_bson_double(name, j.m_value.number_float); case value_t::number_integer: return write_bson_integer(name, j.m_value.number_integer); case value_t::number_unsigned: return write_bson_unsigned(name, j); case value_t::string: return write_bson_string(name, *j.m_value.string); case value_t::null: return write_bson_null(name); // LCOV_EXCL_START case value_t::discarded: default: JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) return; // LCOV_EXCL_STOP } } /*! @brief Calculates the size of the BSON serialization of the given JSON-object @a j. @param[in] value JSON value to serialize @pre value.type() == value_t::object */ static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value) { std::size_t document_size = std::accumulate(value.begin(), value.end(), static_cast(0), [](size_t result, const typename BasicJsonType::object_t::value_type & el) { return result += calc_bson_element_size(el.first, el.second); }); return sizeof(std::int32_t) + document_size + 1ul; } /*! @param[in] value JSON value to serialize @pre value.type() == value_t::object */ void write_bson_object(const typename BasicJsonType::object_t& value) { write_number(static_cast(calc_bson_object_size(value))); for (const auto& el : value) { write_bson_element(el.first, el.second); } oa->write_character(to_char_type(0x00)); } ////////// // CBOR // ////////// static constexpr CharType get_cbor_float_prefix(float /*unused*/) { return to_char_type(0xFA); // Single-Precision Float } static constexpr CharType get_cbor_float_prefix(double /*unused*/) { return to_char_type(0xFB); // Double-Precision Float } ///////////// // MsgPack // ///////////// static constexpr CharType get_msgpack_float_prefix(float /*unused*/) { return to_char_type(0xCA); // float 32 } static constexpr CharType get_msgpack_float_prefix(double /*unused*/) { return to_char_type(0xCB); // float 64 } //////////// // UBJSON // //////////// // UBJSON: write number (floating point) template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, const bool add_prefix) { if (add_prefix) { oa->write_character(get_ubjson_float_prefix(n)); } write_number(n); } // UBJSON: write number (unsigned integer) template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, const bool add_prefix) { if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('i')); // int8 } write_number(static_cast(n)); } else if (n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('U')); // uint8 } write_number(static_cast(n)); } else if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('I')); // int16 } write_number(static_cast(n)); } else if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('l')); // int32 } write_number(static_cast(n)); } else if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('L')); // int64 } write_number(static_cast(n)); } else { if (add_prefix) { oa->write_character(to_char_type('H')); // high-precision number } const auto number = BasicJsonType(n).dump(); write_number_with_ubjson_prefix(number.size(), true); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); } } } // UBJSON: write number (signed integer) template < typename NumberType, typename std::enable_if < std::is_signed::value&& !std::is_floating_point::value, int >::type = 0 > void write_number_with_ubjson_prefix(const NumberType n, const bool add_prefix) { if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('i')); // int8 } write_number(static_cast(n)); } else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('U')); // uint8 } write_number(static_cast(n)); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('I')); // int16 } write_number(static_cast(n)); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('l')); // int32 } write_number(static_cast(n)); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('L')); // int64 } write_number(static_cast(n)); } // LCOV_EXCL_START else { if (add_prefix) { oa->write_character(to_char_type('H')); // high-precision number } const auto number = BasicJsonType(n).dump(); write_number_with_ubjson_prefix(number.size(), true); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); } } // LCOV_EXCL_STOP } /*! @brief determine the type prefix of container values */ CharType ubjson_prefix(const BasicJsonType& j) const noexcept { switch (j.type()) { case value_t::null: return 'Z'; case value_t::boolean: return j.m_value.boolean ? 'T' : 'F'; case value_t::number_integer: { if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'i'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'U'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'I'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'l'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'L'; } // anything else is treated as high-precision number return 'H'; // LCOV_EXCL_LINE } case value_t::number_unsigned: { if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'i'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'U'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'I'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'l'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'L'; } // anything else is treated as high-precision number return 'H'; // LCOV_EXCL_LINE } case value_t::number_float: return get_ubjson_float_prefix(j.m_value.number_float); case value_t::string: return 'S'; case value_t::array: // fallthrough case value_t::binary: return '['; case value_t::object: return '{'; case value_t::discarded: default: // discarded values return 'N'; } } static constexpr CharType get_ubjson_float_prefix(float /*unused*/) { return 'd'; // float 32 } static constexpr CharType get_ubjson_float_prefix(double /*unused*/) { return 'D'; // float 64 } /////////////////////// // Utility functions // /////////////////////// /* @brief write a number to output input @param[in] n number of type @a NumberType @tparam NumberType the type of the number @tparam OutputIsLittleEndian Set to true if output data is required to be little endian @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ template void write_number(const NumberType n) { // step 1: write number to array of length NumberType std::array vec{}; std::memcpy(vec.data(), &n, sizeof(NumberType)); // step 2: write array to output (with possible reordering) if (is_little_endian != OutputIsLittleEndian) { // reverse byte order prior to conversion if necessary std::reverse(vec.begin(), vec.end()); } oa->write_characters(vec.data(), sizeof(NumberType)); } void write_compact_float(const number_float_t n, detail::input_format_t format) { #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif if (static_cast(n) >= static_cast(std::numeric_limits::lowest()) && static_cast(n) <= static_cast((std::numeric_limits::max)()) && static_cast(static_cast(n)) == static_cast(n)) { oa->write_character(format == detail::input_format_t::cbor ? get_cbor_float_prefix(static_cast(n)) : get_msgpack_float_prefix(static_cast(n))); write_number(static_cast(n)); } else { oa->write_character(format == detail::input_format_t::cbor ? get_cbor_float_prefix(n) : get_msgpack_float_prefix(n)); write_number(n); } #ifdef __GNUC__ #pragma GCC diagnostic pop #endif } public: // The following to_char_type functions are implement the conversion // between uint8_t and CharType. In case CharType is not unsigned, // such a conversion is required to allow values greater than 128. // See for a discussion. template < typename C = CharType, enable_if_t < std::is_signed::value && std::is_signed::value > * = nullptr > static constexpr CharType to_char_type(std::uint8_t x) noexcept { return *reinterpret_cast(&x); } template < typename C = CharType, enable_if_t < std::is_signed::value && std::is_unsigned::value > * = nullptr > static CharType to_char_type(std::uint8_t x) noexcept { static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); static_assert(std::is_trivial::value, "CharType must be trivial"); CharType result; std::memcpy(&result, &x, sizeof(x)); return result; } template::value>* = nullptr> static constexpr CharType to_char_type(std::uint8_t x) noexcept { return x; } template < typename InputCharType, typename C = CharType, enable_if_t < std::is_signed::value && std::is_signed::value && std::is_same::type>::value > * = nullptr > static constexpr CharType to_char_type(InputCharType x) noexcept { return x; } private: /// whether we can assume little endianness const bool is_little_endian = little_endianness(); /// the output output_adapter_t oa = nullptr; }; } // namespace detail } // namespace nlohmann // #include // #include #include // reverse, remove, fill, find, none_of #include // array #include // localeconv, lconv #include // labs, isfinite, isnan, signbit #include // size_t, ptrdiff_t #include // uint8_t #include // snprintf #include // numeric_limits #include // string, char_traits #include // setfill, setw #include // stringstream #include // is_same #include // move // #include #include // array #include // signbit, isfinite #include // intN_t, uintN_t #include // memcpy, memmove #include // numeric_limits #include // conditional // #include namespace nlohmann { namespace detail { /*! @brief implements the Grisu2 algorithm for binary to decimal floating-point conversion. This implementation is a slightly modified version of the reference implementation which may be obtained from http://florian.loitsch.com/publications (bench.tar.gz). The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. For a detailed description of the algorithm see: [1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation, PLDI 2010 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation, PLDI 1996 */ namespace dtoa_impl { template Target reinterpret_bits(const Source source) { static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); Target target; std::memcpy(&target, &source, sizeof(Source)); return target; } struct diyfp // f * 2^e { static constexpr int kPrecision = 64; // = q std::uint64_t f = 0; int e = 0; constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} /*! @brief returns x - y @pre x.e == y.e and x.f >= y.f */ static diyfp sub(const diyfp& x, const diyfp& y) noexcept { JSON_ASSERT(x.e == y.e); JSON_ASSERT(x.f >= y.f); return {x.f - y.f, x.e}; } /*! @brief returns x * y @note The result is rounded. (Only the upper q bits are returned.) */ static diyfp mul(const diyfp& x, const diyfp& y) noexcept { static_assert(kPrecision == 64, "internal error"); // Computes: // f = round((x.f * y.f) / 2^q) // e = x.e + y.e + q // Emulate the 64-bit * 64-bit multiplication: // // p = u * v // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) // // (Since Q might be larger than 2^32 - 1) // // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) // // (Q_hi + H does not overflow a 64-bit int) // // = p_lo + 2^64 p_hi const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; const std::uint64_t u_hi = x.f >> 32u; const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; const std::uint64_t v_hi = y.f >> 32u; const std::uint64_t p0 = u_lo * v_lo; const std::uint64_t p1 = u_lo * v_hi; const std::uint64_t p2 = u_hi * v_lo; const std::uint64_t p3 = u_hi * v_hi; const std::uint64_t p0_hi = p0 >> 32u; const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; const std::uint64_t p1_hi = p1 >> 32u; const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; const std::uint64_t p2_hi = p2 >> 32u; std::uint64_t Q = p0_hi + p1_lo + p2_lo; // The full product might now be computed as // // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) // p_lo = p0_lo + (Q << 32) // // But in this particular case here, the full p_lo is not required. // Effectively we only need to add the highest bit in p_lo to p_hi (and // Q_hi + 1 does not overflow). Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); return {h, x.e + y.e + 64}; } /*! @brief normalize x such that the significand is >= 2^(q-1) @pre x.f != 0 */ static diyfp normalize(diyfp x) noexcept { JSON_ASSERT(x.f != 0); while ((x.f >> 63u) == 0) { x.f <<= 1u; x.e--; } return x; } /*! @brief normalize x such that the result has the exponent E @pre e >= x.e and the upper e - x.e bits of x.f must be zero. */ static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept { const int delta = x.e - target_exponent; JSON_ASSERT(delta >= 0); JSON_ASSERT(((x.f << delta) >> delta) == x.f); return {x.f << delta, target_exponent}; } }; struct boundaries { diyfp w; diyfp minus; diyfp plus; }; /*! Compute the (normalized) diyfp representing the input number 'value' and its boundaries. @pre value must be finite and positive */ template boundaries compute_boundaries(FloatType value) { JSON_ASSERT(std::isfinite(value)); JSON_ASSERT(value > 0); // Convert the IEEE representation into a diyfp. // // If v is denormal: // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) // If v is normalized: // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) static_assert(std::numeric_limits::is_iec559, "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); constexpr int kMinExp = 1 - kBias; constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) using bits_type = typename std::conditional::type; const auto bits = static_cast(reinterpret_bits(value)); const std::uint64_t E = bits >> (kPrecision - 1); const std::uint64_t F = bits & (kHiddenBit - 1); const bool is_denormal = E == 0; const diyfp v = is_denormal ? diyfp(F, kMinExp) : diyfp(F + kHiddenBit, static_cast(E) - kBias); // Compute the boundaries m- and m+ of the floating-point value // v = f * 2^e. // // Determine v- and v+, the floating-point predecessor and successor if v, // respectively. // // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) // // v+ = v + 2^e // // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ // between m- and m+ round to v, regardless of how the input rounding // algorithm breaks ties. // // ---+-------------+-------------+-------------+-------------+--- (A) // v- m- v m+ v+ // // -----------------+------+------+-------------+-------------+--- (B) // v- m- v m+ v+ const bool lower_boundary_is_closer = F == 0 && E > 1; const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); const diyfp m_minus = lower_boundary_is_closer ? diyfp(4 * v.f - 1, v.e - 2) // (B) : diyfp(2 * v.f - 1, v.e - 1); // (A) // Determine the normalized w+ = m+. const diyfp w_plus = diyfp::normalize(m_plus); // Determine w- = m- such that e_(w-) = e_(w+). const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); return {diyfp::normalize(v), w_minus, w_plus}; } // Given normalized diyfp w, Grisu needs to find a (normalized) cached // power-of-ten c, such that the exponent of the product c * w = f * 2^e lies // within a certain range [alpha, gamma] (Definition 3.2 from [1]) // // alpha <= e = e_c + e_w + q <= gamma // // or // // f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q // <= f_c * f_w * 2^gamma // // Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies // // 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma // // or // // 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) // // The choice of (alpha,gamma) determines the size of the table and the form of // the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well // in practice: // // The idea is to cut the number c * w = f * 2^e into two parts, which can be // processed independently: An integral part p1, and a fractional part p2: // // f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e // = (f div 2^-e) + (f mod 2^-e) * 2^e // = p1 + p2 * 2^e // // The conversion of p1 into decimal form requires a series of divisions and // modulos by (a power of) 10. These operations are faster for 32-bit than for // 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be // achieved by choosing // // -e >= 32 or e <= -32 := gamma // // In order to convert the fractional part // // p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... // // into decimal form, the fraction is repeatedly multiplied by 10 and the digits // d[-i] are extracted in order: // // (10 * p2) div 2^-e = d[-1] // (10 * p2) mod 2^-e = d[-2] / 10^1 + ... // // The multiplication by 10 must not overflow. It is sufficient to choose // // 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. // // Since p2 = f mod 2^-e < 2^-e, // // -e <= 60 or e >= -60 := alpha constexpr int kAlpha = -60; constexpr int kGamma = -32; struct cached_power // c = f * 2^e ~= 10^k { std::uint64_t f; int e; int k; }; /*! For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c satisfies (Definition 3.2 from [1]) alpha <= e_c + e + q <= gamma. */ inline cached_power get_cached_power_for_binary_exponent(int e) { // Now // // alpha <= e_c + e + q <= gamma (1) // ==> f_c * 2^alpha <= c * 2^e * 2^q // // and since the c's are normalized, 2^(q-1) <= f_c, // // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) // ==> 2^(alpha - e - 1) <= c // // If c were an exact power of ten, i.e. c = 10^k, one may determine k as // // k = ceil( log_10( 2^(alpha - e - 1) ) ) // = ceil( (alpha - e - 1) * log_10(2) ) // // From the paper: // "In theory the result of the procedure could be wrong since c is rounded, // and the computation itself is approximated [...]. In practice, however, // this simple function is sufficient." // // For IEEE double precision floating-point numbers converted into // normalized diyfp's w = f * 2^e, with q = 64, // // e >= -1022 (min IEEE exponent) // -52 (p - 1) // -52 (p - 1, possibly normalize denormal IEEE numbers) // -11 (normalize the diyfp) // = -1137 // // and // // e <= +1023 (max IEEE exponent) // -52 (p - 1) // -11 (normalize the diyfp) // = 960 // // This binary exponent range [-1137,960] results in a decimal exponent // range [-307,324]. One does not need to store a cached power for each // k in this range. For each such k it suffices to find a cached power // such that the exponent of the product lies in [alpha,gamma]. // This implies that the difference of the decimal exponents of adjacent // table entries must be less than or equal to // // floor( (gamma - alpha) * log_10(2) ) = 8. // // (A smaller distance gamma-alpha would require a larger table.) // NB: // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. constexpr int kCachedPowersMinDecExp = -300; constexpr int kCachedPowersDecStep = 8; static constexpr std::array kCachedPowers = { { { 0xAB70FE17C79AC6CA, -1060, -300 }, { 0xFF77B1FCBEBCDC4F, -1034, -292 }, { 0xBE5691EF416BD60C, -1007, -284 }, { 0x8DD01FAD907FFC3C, -980, -276 }, { 0xD3515C2831559A83, -954, -268 }, { 0x9D71AC8FADA6C9B5, -927, -260 }, { 0xEA9C227723EE8BCB, -901, -252 }, { 0xAECC49914078536D, -874, -244 }, { 0x823C12795DB6CE57, -847, -236 }, { 0xC21094364DFB5637, -821, -228 }, { 0x9096EA6F3848984F, -794, -220 }, { 0xD77485CB25823AC7, -768, -212 }, { 0xA086CFCD97BF97F4, -741, -204 }, { 0xEF340A98172AACE5, -715, -196 }, { 0xB23867FB2A35B28E, -688, -188 }, { 0x84C8D4DFD2C63F3B, -661, -180 }, { 0xC5DD44271AD3CDBA, -635, -172 }, { 0x936B9FCEBB25C996, -608, -164 }, { 0xDBAC6C247D62A584, -582, -156 }, { 0xA3AB66580D5FDAF6, -555, -148 }, { 0xF3E2F893DEC3F126, -529, -140 }, { 0xB5B5ADA8AAFF80B8, -502, -132 }, { 0x87625F056C7C4A8B, -475, -124 }, { 0xC9BCFF6034C13053, -449, -116 }, { 0x964E858C91BA2655, -422, -108 }, { 0xDFF9772470297EBD, -396, -100 }, { 0xA6DFBD9FB8E5B88F, -369, -92 }, { 0xF8A95FCF88747D94, -343, -84 }, { 0xB94470938FA89BCF, -316, -76 }, { 0x8A08F0F8BF0F156B, -289, -68 }, { 0xCDB02555653131B6, -263, -60 }, { 0x993FE2C6D07B7FAC, -236, -52 }, { 0xE45C10C42A2B3B06, -210, -44 }, { 0xAA242499697392D3, -183, -36 }, { 0xFD87B5F28300CA0E, -157, -28 }, { 0xBCE5086492111AEB, -130, -20 }, { 0x8CBCCC096F5088CC, -103, -12 }, { 0xD1B71758E219652C, -77, -4 }, { 0x9C40000000000000, -50, 4 }, { 0xE8D4A51000000000, -24, 12 }, { 0xAD78EBC5AC620000, 3, 20 }, { 0x813F3978F8940984, 30, 28 }, { 0xC097CE7BC90715B3, 56, 36 }, { 0x8F7E32CE7BEA5C70, 83, 44 }, { 0xD5D238A4ABE98068, 109, 52 }, { 0x9F4F2726179A2245, 136, 60 }, { 0xED63A231D4C4FB27, 162, 68 }, { 0xB0DE65388CC8ADA8, 189, 76 }, { 0x83C7088E1AAB65DB, 216, 84 }, { 0xC45D1DF942711D9A, 242, 92 }, { 0x924D692CA61BE758, 269, 100 }, { 0xDA01EE641A708DEA, 295, 108 }, { 0xA26DA3999AEF774A, 322, 116 }, { 0xF209787BB47D6B85, 348, 124 }, { 0xB454E4A179DD1877, 375, 132 }, { 0x865B86925B9BC5C2, 402, 140 }, { 0xC83553C5C8965D3D, 428, 148 }, { 0x952AB45CFA97A0B3, 455, 156 }, { 0xDE469FBD99A05FE3, 481, 164 }, { 0xA59BC234DB398C25, 508, 172 }, { 0xF6C69A72A3989F5C, 534, 180 }, { 0xB7DCBF5354E9BECE, 561, 188 }, { 0x88FCF317F22241E2, 588, 196 }, { 0xCC20CE9BD35C78A5, 614, 204 }, { 0x98165AF37B2153DF, 641, 212 }, { 0xE2A0B5DC971F303A, 667, 220 }, { 0xA8D9D1535CE3B396, 694, 228 }, { 0xFB9B7CD9A4A7443C, 720, 236 }, { 0xBB764C4CA7A44410, 747, 244 }, { 0x8BAB8EEFB6409C1A, 774, 252 }, { 0xD01FEF10A657842C, 800, 260 }, { 0x9B10A4E5E9913129, 827, 268 }, { 0xE7109BFBA19C0C9D, 853, 276 }, { 0xAC2820D9623BF429, 880, 284 }, { 0x80444B5E7AA7CF85, 907, 292 }, { 0xBF21E44003ACDD2D, 933, 300 }, { 0x8E679C2F5E44FF8F, 960, 308 }, { 0xD433179D9C8CB841, 986, 316 }, { 0x9E19DB92B4E31BA9, 1013, 324 }, } }; // This computation gives exactly the same results for k as // k = ceil((kAlpha - e - 1) * 0.30102999566398114) // for |e| <= 1500, but doesn't require floating-point operations. // NB: log_10(2) ~= 78913 / 2^18 JSON_ASSERT(e >= -1500); JSON_ASSERT(e <= 1500); const int f = kAlpha - e - 1; const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; JSON_ASSERT(index >= 0); JSON_ASSERT(static_cast(index) < kCachedPowers.size()); const cached_power cached = kCachedPowers[static_cast(index)]; JSON_ASSERT(kAlpha <= cached.e + e + 64); JSON_ASSERT(kGamma >= cached.e + e + 64); return cached; } /*! For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. For n == 0, returns 1 and sets pow10 := 1. */ inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) { // LCOV_EXCL_START if (n >= 1000000000) { pow10 = 1000000000; return 10; } // LCOV_EXCL_STOP if (n >= 100000000) { pow10 = 100000000; return 9; } if (n >= 10000000) { pow10 = 10000000; return 8; } if (n >= 1000000) { pow10 = 1000000; return 7; } if (n >= 100000) { pow10 = 100000; return 6; } if (n >= 10000) { pow10 = 10000; return 5; } if (n >= 1000) { pow10 = 1000; return 4; } if (n >= 100) { pow10 = 100; return 3; } if (n >= 10) { pow10 = 10; return 2; } pow10 = 1; return 1; } inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, std::uint64_t rest, std::uint64_t ten_k) { JSON_ASSERT(len >= 1); JSON_ASSERT(dist <= delta); JSON_ASSERT(rest <= delta); JSON_ASSERT(ten_k > 0); // <--------------------------- delta ----> // <---- dist ---------> // --------------[------------------+-------------------]-------------- // M- w M+ // // ten_k // <------> // <---- rest ----> // --------------[------------------+----+--------------]-------------- // w V // = buf * 10^k // // ten_k represents a unit-in-the-last-place in the decimal representation // stored in buf. // Decrement buf by ten_k while this takes buf closer to w. // The tests are written in this order to avoid overflow in unsigned // integer arithmetic. while (rest < dist && delta - rest >= ten_k && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) { JSON_ASSERT(buf[len - 1] != '0'); buf[len - 1]--; rest += ten_k; } } /*! Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. M- and M+ must be normalized and share the same exponent -60 <= e <= -32. */ inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, diyfp M_minus, diyfp w, diyfp M_plus) { static_assert(kAlpha >= -60, "internal error"); static_assert(kGamma <= -32, "internal error"); // Generates the digits (and the exponent) of a decimal floating-point // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. // // <--------------------------- delta ----> // <---- dist ---------> // --------------[------------------+-------------------]-------------- // M- w M+ // // Grisu2 generates the digits of M+ from left to right and stops as soon as // V is in [M-,M+]. JSON_ASSERT(M_plus.e >= kAlpha); JSON_ASSERT(M_plus.e <= kGamma); std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): // // M+ = f * 2^e // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e // = ((p1 ) * 2^-e + (p2 )) * 2^e // = p1 + p2 * 2^e const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); auto p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e // 1) // // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] JSON_ASSERT(p1 > 0); std::uint32_t pow10{}; const int k = find_largest_pow10(p1, pow10); // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) // // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) // // M+ = p1 + p2 * 2^e // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e // = d[k-1] * 10^(k-1) + ( rest) * 2^e // // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) // // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] // // but stop as soon as // // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e int n = k; while (n > 0) { // Invariants: // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) // pow10 = 10^(n-1) <= p1 < 10^n // const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) // // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) // JSON_ASSERT(d <= 9); buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d // // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) // p1 = r; n--; // // M+ = buffer * 10^n + (p1 + p2 * 2^e) // pow10 = 10^n // // Now check if enough digits have been generated. // Compute // // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e // // Note: // Since rest and delta share the same exponent e, it suffices to // compare the significands. const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; if (rest <= delta) { // V = buffer * 10^n, with M- <= V <= M+. decimal_exponent += n; // We may now just stop. But instead look if the buffer could be // decremented to bring V closer to w. // // pow10 = 10^n is now 1 ulp in the decimal representation V. // The rounding procedure works with diyfp's with an implicit // exponent of e. // // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e // const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; grisu2_round(buffer, length, dist, delta, rest, ten_n); return; } pow10 /= 10; // // pow10 = 10^(n-1) <= p1 < 10^n // Invariants restored. } // 2) // // The digits of the integral part have been generated: // // M+ = d[k-1]...d[1]d[0] + p2 * 2^e // = buffer + p2 * 2^e // // Now generate the digits of the fractional part p2 * 2^e. // // Note: // No decimal point is generated: the exponent is adjusted instead. // // p2 actually represents the fraction // // p2 * 2^e // = p2 / 2^-e // = d[-1] / 10^1 + d[-2] / 10^2 + ... // // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) // // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) // // using // // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) // = ( d) * 2^-e + ( r) // // or // 10^m * p2 * 2^e = d + r * 2^e // // i.e. // // M+ = buffer + p2 * 2^e // = buffer + 10^-m * (d + r * 2^e) // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e // // and stop as soon as 10^-m * r * 2^e <= delta * 2^e JSON_ASSERT(p2 > delta); int m = 0; for (;;) { // Invariant: // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e // = buffer * 10^-m + 10^-m * (p2 ) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e // JSON_ASSERT(p2 <= (std::numeric_limits::max)() / 10); p2 *= 10; const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e // // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e // JSON_ASSERT(d <= 9); buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d // // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e // p2 = r; m++; // // M+ = buffer * 10^-m + 10^-m * p2 * 2^e // Invariant restored. // Check if enough digits have been generated. // // 10^-m * p2 * 2^e <= delta * 2^e // p2 * 2^e <= 10^m * delta * 2^e // p2 <= 10^m * delta delta *= 10; dist *= 10; if (p2 <= delta) { break; } } // V = buffer * 10^-m, with M- <= V <= M+. decimal_exponent -= m; // 1 ulp in the decimal representation is now 10^-m. // Since delta and dist are now scaled by 10^m, we need to do the // same with ulp in order to keep the units in sync. // // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e // const std::uint64_t ten_m = one.f; grisu2_round(buffer, length, dist, delta, p2, ten_m); // By construction this algorithm generates the shortest possible decimal // number (Loitsch, Theorem 6.2) which rounds back to w. // For an input number of precision p, at least // // N = 1 + ceil(p * log_10(2)) // // decimal digits are sufficient to identify all binary floating-point // numbers (Matula, "In-and-Out conversions"). // This implies that the algorithm does not produce more than N decimal // digits. // // N = 17 for p = 53 (IEEE double precision) // N = 9 for p = 24 (IEEE single precision) } /*! v = buf * 10^decimal_exponent len is the length of the buffer (number of decimal digits) The buffer must be large enough, i.e. >= max_digits10. */ JSON_HEDLEY_NON_NULL(1) inline void grisu2(char* buf, int& len, int& decimal_exponent, diyfp m_minus, diyfp v, diyfp m_plus) { JSON_ASSERT(m_plus.e == m_minus.e); JSON_ASSERT(m_plus.e == v.e); // --------(-----------------------+-----------------------)-------- (A) // m- v m+ // // --------------------(-----------+-----------------------)-------- (B) // m- v m+ // // First scale v (and m- and m+) such that the exponent is in the range // [alpha, gamma]. const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] const diyfp w = diyfp::mul(v, c_minus_k); const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); // ----(---+---)---------------(---+---)---------------(---+---)---- // w- w w+ // = c*m- = c*v = c*m+ // // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and // w+ are now off by a small amount. // In fact: // // w - v * 10^k < 1 ulp // // To account for this inaccuracy, add resp. subtract 1 ulp. // // --------+---[---------------(---+---)---------------]---+-------- // w- M- w M+ w+ // // Now any number in [M-, M+] (bounds included) will round to w when input, // regardless of how the input rounding algorithm breaks ties. // // And digit_gen generates the shortest possible such number in [M-, M+]. // Note that this does not mean that Grisu2 always generates the shortest // possible number in the interval (m-, m+). const diyfp M_minus(w_minus.f + 1, w_minus.e); const diyfp M_plus (w_plus.f - 1, w_plus.e ); decimal_exponent = -cached.k; // = -(-k) = k grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); } /*! v = buf * 10^decimal_exponent len is the length of the buffer (number of decimal digits) The buffer must be large enough, i.e. >= max_digits10. */ template JSON_HEDLEY_NON_NULL(1) void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) { static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, "internal error: not enough precision"); JSON_ASSERT(std::isfinite(value)); JSON_ASSERT(value > 0); // If the neighbors (and boundaries) of 'value' are always computed for double-precision // numbers, all float's can be recovered using strtod (and strtof). However, the resulting // decimal representations are not exactly "short". // // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) // says "value is converted to a string as if by std::sprintf in the default ("C") locale" // and since sprintf promotes floats to doubles, I think this is exactly what 'std::to_chars' // does. // On the other hand, the documentation for 'std::to_chars' requires that "parsing the // representation using the corresponding std::from_chars function recovers value exactly". That // indicates that single precision floating-point numbers should be recovered using // 'std::strtof'. // // NB: If the neighbors are computed for single-precision numbers, there is a single float // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision // value is off by 1 ulp. #if 0 const boundaries w = compute_boundaries(static_cast(value)); #else const boundaries w = compute_boundaries(value); #endif grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); } /*! @brief appends a decimal representation of e to buf @return a pointer to the element following the exponent. @pre -1000 < e < 1000 */ JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* append_exponent(char* buf, int e) { JSON_ASSERT(e > -1000); JSON_ASSERT(e < 1000); if (e < 0) { e = -e; *buf++ = '-'; } else { *buf++ = '+'; } auto k = static_cast(e); if (k < 10) { // Always print at least two digits in the exponent. // This is for compatibility with printf("%g"). *buf++ = '0'; *buf++ = static_cast('0' + k); } else if (k < 100) { *buf++ = static_cast('0' + k / 10); k %= 10; *buf++ = static_cast('0' + k); } else { *buf++ = static_cast('0' + k / 100); k %= 100; *buf++ = static_cast('0' + k / 10); k %= 10; *buf++ = static_cast('0' + k); } return buf; } /*! @brief prettify v = buf * 10^decimal_exponent If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point notation. Otherwise it will be printed in exponential notation. @pre min_exp < 0 @pre max_exp > 0 */ JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* format_buffer(char* buf, int len, int decimal_exponent, int min_exp, int max_exp) { JSON_ASSERT(min_exp < 0); JSON_ASSERT(max_exp > 0); const int k = len; const int n = len + decimal_exponent; // v = buf * 10^(n-k) // k is the length of the buffer (number of decimal digits) // n is the position of the decimal point relative to the start of the buffer. if (k <= n && n <= max_exp) { // digits[000] // len <= max_exp + 2 std::memset(buf + k, '0', static_cast(n) - static_cast(k)); // Make it look like a floating-point number (#362, #378) buf[n + 0] = '.'; buf[n + 1] = '0'; return buf + (static_cast(n) + 2); } if (0 < n && n <= max_exp) { // dig.its // len <= max_digits10 + 1 JSON_ASSERT(k > n); std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); buf[n] = '.'; return buf + (static_cast(k) + 1U); } if (min_exp < n && n <= 0) { // 0.[000]digits // len <= 2 + (-min_exp - 1) + max_digits10 std::memmove(buf + (2 + static_cast(-n)), buf, static_cast(k)); buf[0] = '0'; buf[1] = '.'; std::memset(buf + 2, '0', static_cast(-n)); return buf + (2U + static_cast(-n) + static_cast(k)); } if (k == 1) { // dE+123 // len <= 1 + 5 buf += 1; } else { // d.igitsE+123 // len <= max_digits10 + 1 + 5 std::memmove(buf + 2, buf + 1, static_cast(k) - 1); buf[1] = '.'; buf += 1 + static_cast(k); } *buf++ = 'e'; return append_exponent(buf, n - 1); } } // namespace dtoa_impl /*! @brief generates a decimal representation of the floating-point number value in [first, last). The format of the resulting decimal representation is similar to printf's %g format. Returns an iterator pointing past-the-end of the decimal representation. @note The input number must be finite, i.e. NaN's and Inf's are not supported. @note The buffer must be large enough. @note The result is NOT null-terminated. */ template JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_RETURNS_NON_NULL char* to_chars(char* first, const char* last, FloatType value) { static_cast(last); // maybe unused - fix warning JSON_ASSERT(std::isfinite(value)); // Use signbit(value) instead of (value < 0) since signbit works for -0. if (std::signbit(value)) { value = -value; *first++ = '-'; } #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif if (value == 0) // +-0 { *first++ = '0'; // Make it look like a floating-point number (#362, #378) *first++ = '.'; *first++ = '0'; return first; } #ifdef __GNUC__ #pragma GCC diagnostic pop #endif JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); // Compute v = buffer * 10^decimal_exponent. // The decimal digits are stored in the buffer, which needs to be interpreted // as an unsigned decimal integer. // len is the length of the buffer, i.e. the number of decimal digits. int len = 0; int decimal_exponent = 0; dtoa_impl::grisu2(first, len, decimal_exponent, value); JSON_ASSERT(len <= std::numeric_limits::max_digits10); // Format the buffer like printf("%.*g", prec, value) constexpr int kMinExp = -4; // Use digits10 here to increase compatibility with version 2. constexpr int kMaxExp = std::numeric_limits::digits10; JSON_ASSERT(last - first >= kMaxExp + 2); JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); } } // namespace detail } // namespace nlohmann // #include // #include // #include // #include // #include // #include namespace nlohmann { namespace detail { /////////////////// // serialization // /////////////////// /// how to treat decoding errors enum class error_handler_t { strict, ///< throw a type_error exception in case of invalid UTF-8 replace, ///< replace invalid UTF-8 sequences with U+FFFD ignore ///< ignore invalid UTF-8 sequences }; template class serializer { using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using binary_char_t = typename BasicJsonType::binary_t::value_type; static constexpr std::uint8_t UTF8_ACCEPT = 0; static constexpr std::uint8_t UTF8_REJECT = 1; public: /*! @param[in] s output stream to serialize to @param[in] ichar indentation character to use @param[in] error_handler_ how to react on decoding errors */ serializer(output_adapter_t s, const char ichar, error_handler_t error_handler_ = error_handler_t::strict) : o(std::move(s)) , loc(std::localeconv()) , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) , indent_char(ichar) , indent_string(512, indent_char) , error_handler(error_handler_) {} // delete because of pointer members serializer(const serializer&) = delete; serializer& operator=(const serializer&) = delete; serializer(serializer&&) = delete; serializer& operator=(serializer&&) = delete; ~serializer() = default; /*! @brief internal implementation of the serialization function This function is called by the public member function dump and organizes the serialization internally. The indentation level is propagated as additional parameter. In case of arrays and objects, the function is called recursively. - strings and object keys are escaped using `escape_string()` - integer numbers are converted implicitly via `operator<<` - floating-point numbers are converted to a string using `"%g"` format - binary values are serialized as objects containing the subtype and the byte array @param[in] val value to serialize @param[in] pretty_print whether the output shall be pretty-printed @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters in the output are escaped with `\uXXXX` sequences, and the result consists of ASCII characters only. @param[in] indent_step the indent level @param[in] current_indent the current indent level (only used internally) */ void dump(const BasicJsonType& val, const bool pretty_print, const bool ensure_ascii, const unsigned int indent_step, const unsigned int current_indent = 0) { switch (val.m_type) { case value_t::object: { if (val.m_value.object->empty()) { o->write_characters("{}", 2); return; } if (pretty_print) { o->write_characters("{\n", 2); // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); } // first n-1 elements auto i = val.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); } // last element JSON_ASSERT(i != val.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_value.object->cend()); o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); o->write_character('}'); } else { o->write_character('{'); // first n-1 elements auto i = val.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character(','); } // last element JSON_ASSERT(i != val.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_value.object->cend()); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character('}'); } return; } case value_t::array: { if (val.m_value.array->empty()) { o->write_characters("[]", 2); return; } if (pretty_print) { o->write_characters("[\n", 2); // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); } // first n-1 elements for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) { o->write_characters(indent_string.c_str(), new_indent); dump(*i, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); } // last element JSON_ASSERT(!val.m_value.array->empty()); o->write_characters(indent_string.c_str(), new_indent); dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); o->write_character(']'); } else { o->write_character('['); // first n-1 elements for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) { dump(*i, false, ensure_ascii, indent_step, current_indent); o->write_character(','); } // last element JSON_ASSERT(!val.m_value.array->empty()); dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); o->write_character(']'); } return; } case value_t::string: { o->write_character('\"'); dump_escaped(*val.m_value.string, ensure_ascii); o->write_character('\"'); return; } case value_t::binary: { if (pretty_print) { o->write_characters("{\n", 2); // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); } o->write_characters(indent_string.c_str(), new_indent); o->write_characters("\"bytes\": [", 10); if (!val.m_value.binary->empty()) { for (auto i = val.m_value.binary->cbegin(); i != val.m_value.binary->cend() - 1; ++i) { dump_integer(*i); o->write_characters(", ", 2); } dump_integer(val.m_value.binary->back()); } o->write_characters("],\n", 3); o->write_characters(indent_string.c_str(), new_indent); o->write_characters("\"subtype\": ", 11); if (val.m_value.binary->has_subtype()) { dump_integer(val.m_value.binary->subtype()); } else { o->write_characters("null", 4); } o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); o->write_character('}'); } else { o->write_characters("{\"bytes\":[", 10); if (!val.m_value.binary->empty()) { for (auto i = val.m_value.binary->cbegin(); i != val.m_value.binary->cend() - 1; ++i) { dump_integer(*i); o->write_character(','); } dump_integer(val.m_value.binary->back()); } o->write_characters("],\"subtype\":", 12); if (val.m_value.binary->has_subtype()) { dump_integer(val.m_value.binary->subtype()); o->write_character('}'); } else { o->write_characters("null}", 5); } } return; } case value_t::boolean: { if (val.m_value.boolean) { o->write_characters("true", 4); } else { o->write_characters("false", 5); } return; } case value_t::number_integer: { dump_integer(val.m_value.number_integer); return; } case value_t::number_unsigned: { dump_integer(val.m_value.number_unsigned); return; } case value_t::number_float: { dump_float(val.m_value.number_float); return; } case value_t::discarded: { o->write_characters("", 11); return; } case value_t::null: { o->write_characters("null", 4); return; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } } JSON_PRIVATE_UNLESS_TESTED: /*! @brief dump escaped string Escape a string by replacing certain special characters by a sequence of an escape character (backslash) and another character and other control characters by a sequence of "\u" followed by a four-digit hex representation. The escaped string is written to output stream @a o. @param[in] s the string to escape @param[in] ensure_ascii whether to escape non-ASCII characters with \uXXXX sequences @complexity Linear in the length of string @a s. */ void dump_escaped(const string_t& s, const bool ensure_ascii) { std::uint32_t codepoint{}; std::uint8_t state = UTF8_ACCEPT; std::size_t bytes = 0; // number of bytes written to string_buffer // number of bytes written at the point of the last valid byte std::size_t bytes_after_last_accept = 0; std::size_t undumped_chars = 0; for (std::size_t i = 0; i < s.size(); ++i) { const auto byte = static_cast(s[i]); switch (decode(state, codepoint, byte)) { case UTF8_ACCEPT: // decode found a new code point { switch (codepoint) { case 0x08: // backspace { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'b'; break; } case 0x09: // horizontal tab { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 't'; break; } case 0x0A: // newline { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'n'; break; } case 0x0C: // formfeed { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'f'; break; } case 0x0D: // carriage return { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'r'; break; } case 0x22: // quotation mark { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = '\"'; break; } case 0x5C: // reverse solidus { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = '\\'; break; } default: { // escape control characters (0x00..0x1F) or, if // ensure_ascii parameter is used, non-ASCII characters if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) { if (codepoint <= 0xFFFF) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) static_cast((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x", static_cast(codepoint))); bytes += 6; } else { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) static_cast((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", static_cast(0xD7C0u + (codepoint >> 10u)), static_cast(0xDC00u + (codepoint & 0x3FFu)))); bytes += 12; } } else { // copy byte to buffer (all previous bytes // been copied have in default case above) string_buffer[bytes++] = s[i]; } break; } } // write buffer and reset index; there must be 13 bytes // left, as this is the maximal number of bytes to be // written ("\uxxxx\uxxxx\0") for one code point if (string_buffer.size() - bytes < 13) { o->write_characters(string_buffer.data(), bytes); bytes = 0; } // remember the byte position of this accept bytes_after_last_accept = bytes; undumped_chars = 0; break; } case UTF8_REJECT: // decode found invalid UTF-8 byte { switch (error_handler) { case error_handler_t::strict: { std::stringstream ss; ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << (byte | 0); JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str(), BasicJsonType())); } case error_handler_t::ignore: case error_handler_t::replace: { // in case we saw this character the first time, we // would like to read it again, because the byte // may be OK for itself, but just not OK for the // previous sequence if (undumped_chars > 0) { --i; } // reset length buffer to the last accepted index; // thus removing/ignoring the invalid characters bytes = bytes_after_last_accept; if (error_handler == error_handler_t::replace) { // add a replacement character if (ensure_ascii) { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'u'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'd'; } else { string_buffer[bytes++] = detail::binary_writer::to_char_type('\xEF'); string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBF'); string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBD'); } // write buffer and reset index; there must be 13 bytes // left, as this is the maximal number of bytes to be // written ("\uxxxx\uxxxx\0") for one code point if (string_buffer.size() - bytes < 13) { o->write_characters(string_buffer.data(), bytes); bytes = 0; } bytes_after_last_accept = bytes; } undumped_chars = 0; // continue processing the string state = UTF8_ACCEPT; break; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } break; } default: // decode found yet incomplete multi-byte code point { if (!ensure_ascii) { // code point will not be escaped - copy byte to buffer string_buffer[bytes++] = s[i]; } ++undumped_chars; break; } } } // we finished processing the string if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) { // write buffer if (bytes > 0) { o->write_characters(string_buffer.data(), bytes); } } else { // we finish reading, but do not accept: string was incomplete switch (error_handler) { case error_handler_t::strict: { std::stringstream ss; ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << (static_cast(s.back()) | 0); JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str(), BasicJsonType())); } case error_handler_t::ignore: { // write all accepted bytes o->write_characters(string_buffer.data(), bytes_after_last_accept); break; } case error_handler_t::replace: { // write all accepted bytes o->write_characters(string_buffer.data(), bytes_after_last_accept); // add a replacement character if (ensure_ascii) { o->write_characters("\\ufffd", 6); } else { o->write_characters("\xEF\xBF\xBD", 3); } break; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } } } private: /*! @brief count digits Count the number of decimal (base 10) digits for an input unsigned integer. @param[in] x unsigned integer number to count its digits @return number of decimal digits */ inline unsigned int count_digits(number_unsigned_t x) noexcept { unsigned int n_digits = 1; for (;;) { if (x < 10) { return n_digits; } if (x < 100) { return n_digits + 1; } if (x < 1000) { return n_digits + 2; } if (x < 10000) { return n_digits + 3; } x = x / 10000u; n_digits += 4; } } // templates to avoid warnings about useless casts template ::value, int> = 0> bool is_negative_number(NumberType x) { return x < 0; } template < typename NumberType, enable_if_t ::value, int > = 0 > bool is_negative_number(NumberType /*unused*/) { return false; } /*! @brief dump an integer Dump a given integer to output stream @a o. Works internally with @a number_buffer. @param[in] x integer number (signed or unsigned) to dump @tparam NumberType either @a number_integer_t or @a number_unsigned_t */ template < typename NumberType, detail::enable_if_t < std::is_integral::value || std::is_same::value || std::is_same::value || std::is_same::value, int > = 0 > void dump_integer(NumberType x) { static constexpr std::array, 100> digits_to_99 { { {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, } }; // special case for "0" if (x == 0) { o->write_character('0'); return; } // use a pointer to fill the buffer auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg) number_unsigned_t abs_value; unsigned int n_chars{}; if (is_negative_number(x)) { *buffer_ptr = '-'; abs_value = remove_sign(static_cast(x)); // account one more byte for the minus sign n_chars = 1 + count_digits(abs_value); } else { abs_value = static_cast(x); n_chars = count_digits(abs_value); } // spare 1 byte for '\0' JSON_ASSERT(n_chars < number_buffer.size() - 1); // jump to the end to generate the string from backward, // so we later avoid reversing the result buffer_ptr += n_chars; // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu // See: https://www.youtube.com/watch?v=o4-CwDo2zpg while (abs_value >= 100) { const auto digits_index = static_cast((abs_value % 100)); abs_value /= 100; *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } if (abs_value >= 10) { const auto digits_index = static_cast(abs_value); *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } else { *(--buffer_ptr) = static_cast('0' + abs_value); } o->write_characters(number_buffer.data(), n_chars); } /*! @brief dump a floating-point number Dump a given floating-point number to output stream @a o. Works internally with @a number_buffer. @param[in] x floating-point number to dump */ void dump_float(number_float_t x) { // NaN / inf if (!std::isfinite(x)) { o->write_characters("null", 4); return; } // If number_float_t is an IEEE-754 single or double precision number, // use the Grisu2 algorithm to produce short numbers which are // guaranteed to round-trip, using strtof and strtod, resp. // // NB: The test below works if == . static constexpr bool is_ieee_single_or_double = (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) || (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024); dump_float(x, std::integral_constant()); } void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) { auto* begin = number_buffer.data(); auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); o->write_characters(begin, static_cast(end - begin)); } void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) { // get number of digits for a float -> text -> float round-trip static constexpr auto d = std::numeric_limits::max_digits10; // the actual conversion // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); // negative value indicates an error JSON_ASSERT(len > 0); // check if buffer was large enough JSON_ASSERT(static_cast(len) < number_buffer.size()); // erase thousands separator if (thousands_sep != '\0') { // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081 const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep); std::fill(end, number_buffer.end(), '\0'); JSON_ASSERT((end - number_buffer.begin()) <= len); len = (end - number_buffer.begin()); } // convert decimal point to '.' if (decimal_point != '\0' && decimal_point != '.') { // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081 const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); if (dec_pos != number_buffer.end()) { *dec_pos = '.'; } } o->write_characters(number_buffer.data(), static_cast(len)); // determine if we need to append ".0" const bool value_is_int_like = std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, [](char c) { return c == '.' || c == 'e'; }); if (value_is_int_like) { o->write_characters(".0", 2); } } /*! @brief check whether a string is UTF-8 encoded The function checks each byte of a string whether it is UTF-8 encoded. The result of the check is stored in the @a state parameter. The function must be called initially with state 0 (accept). State 1 means the string must be rejected, because the current byte is not allowed. If the string is completely processed, but the state is non-zero, the string ended prematurely; that is, the last byte indicated more bytes should have followed. @param[in,out] state the state of the decoding @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) @param[in] byte next byte to decode @return new state @note The function has been edited: a std::array is used. @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept { static const std::array utf8d = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 } }; JSON_ASSERT(byte < utf8d.size()); const std::uint8_t type = utf8d[byte]; codep = (state != UTF8_ACCEPT) ? (byte & 0x3fu) | (codep << 6u) : (0xFFu >> type) & (byte); std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); JSON_ASSERT(index < 400); state = utf8d[index]; return state; } /* * Overload to make the compiler happy while it is instantiating * dump_integer for number_unsigned_t. * Must never be called. */ number_unsigned_t remove_sign(number_unsigned_t x) { JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE return x; // LCOV_EXCL_LINE } /* * Helper function for dump_integer * * This function takes a negative signed integer and returns its absolute * value as unsigned integer. The plus/minus shuffling is necessary as we can * not directly remove the sign of an arbitrary signed integer as the * absolute values of INT_MIN and INT_MAX are usually not the same. See * #1708 for details. */ inline number_unsigned_t remove_sign(number_integer_t x) noexcept { JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); // NOLINT(misc-redundant-expression) return static_cast(-(x + 1)) + 1; } private: /// the output of the serializer output_adapter_t o = nullptr; /// a (hopefully) large enough character buffer std::array number_buffer{{}}; /// the locale const std::lconv* loc = nullptr; /// the locale's thousand separator character const char thousands_sep = '\0'; /// the locale's decimal point character const char decimal_point = '\0'; /// string buffer std::array string_buffer{{}}; /// the indentation character const char indent_char; /// the indentation string string_t indent_string; /// error_handler how to react on decoding errors const error_handler_t error_handler; }; } // namespace detail } // namespace nlohmann // #include // #include // #include #include // less #include // initializer_list #include // input_iterator_tag, iterator_traits #include // allocator #include // for out_of_range #include // enable_if, is_convertible #include // pair #include // vector // #include namespace nlohmann { /// ordered_map: a minimal map-like container that preserves insertion order /// for use within nlohmann::basic_json template , class Allocator = std::allocator>> struct ordered_map : std::vector, Allocator> { using key_type = Key; using mapped_type = T; using Container = std::vector, Allocator>; using iterator = typename Container::iterator; using const_iterator = typename Container::const_iterator; using size_type = typename Container::size_type; using value_type = typename Container::value_type; // Explicit constructors instead of `using Container::Container` // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) ordered_map(const Allocator& alloc = Allocator()) : Container{alloc} {} template ordered_map(It first, It last, const Allocator& alloc = Allocator()) : Container{first, last, alloc} {} ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) : Container{init, alloc} {} std::pair emplace(const key_type& key, T&& t) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return {it, false}; } } Container::emplace_back(key, t); return {--this->end(), true}; } T& operator[](const Key& key) { return emplace(key, T{}).first->second; } const T& operator[](const Key& key) const { return at(key); } T& at(const Key& key) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it->second; } } JSON_THROW(std::out_of_range("key not found")); } const T& at(const Key& key) const { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it->second; } } JSON_THROW(std::out_of_range("key not found")); } size_type erase(const Key& key) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { // Since we cannot move const Keys, re-construct them in place for (auto next = it; ++next != this->end(); ++it) { it->~value_type(); // Destroy but keep allocation new (&*it) value_type{std::move(*next)}; } Container::pop_back(); return 1; } } return 0; } iterator erase(iterator pos) { return erase(pos, std::next(pos)); } iterator erase(iterator first, iterator last) { const auto elements_affected = std::distance(first, last); const auto offset = std::distance(Container::begin(), first); // This is the start situation. We need to delete elements_affected // elements (3 in this example: e, f, g), and need to return an // iterator past the last deleted element (h in this example). // Note that offset is the distance from the start of the vector // to first. We will need this later. // [ a, b, c, d, e, f, g, h, i, j ] // ^ ^ // first last // Since we cannot move const Keys, we re-construct them in place. // We start at first and re-construct (viz. copy) the elements from // the back of the vector. Example for first iteration: // ,--------. // v | destroy e and re-construct with h // [ a, b, c, d, e, f, g, h, i, j ] // ^ ^ // it it + elements_affected for (auto it = first; std::next(it, elements_affected) != Container::end(); ++it) { it->~value_type(); // destroy but keep allocation new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // "move" next element to it } // [ a, b, c, d, h, i, j, h, i, j ] // ^ ^ // first last // remove the unneeded elements at the end of the vector Container::resize(this->size() - static_cast(elements_affected)); // [ a, b, c, d, h, i, j ] // ^ ^ // first last // first is now pointing past the last deleted element, but we cannot // use this iterator, because it may have been invalidated by the // resize call. Instead, we can return begin() + offset. return Container::begin() + offset; } size_type count(const Key& key) const { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return 1; } } return 0; } iterator find(const Key& key) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it; } } return Container::end(); } const_iterator find(const Key& key) const { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it; } } return Container::end(); } std::pair insert( value_type&& value ) { return emplace(value.first, std::move(value.second)); } std::pair insert( const value_type& value ) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == value.first) { return {it, false}; } } Container::push_back(value); return {--this->end(), true}; } template using require_input_iter = typename std::enable_if::iterator_category, std::input_iterator_tag>::value>::type; template> void insert(InputIt first, InputIt last) { for (auto it = first; it != last; ++it) { insert(*it); } } }; } // namespace nlohmann #if defined(JSON_HAS_CPP_17) #include #endif /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann @since version 1.0.0 */ namespace nlohmann { /*! @brief a class to store JSON values @internal @invariant The member variables @a m_value and @a m_type have the following relationship: - If `m_type == value_t::object`, then `m_value.object != nullptr`. - If `m_type == value_t::array`, then `m_value.array != nullptr`. - If `m_type == value_t::string`, then `m_value.string != nullptr`. The invariants are checked by member function assert_invariant(). @note ObjectType trick from https://stackoverflow.com/a/9860911 @endinternal @since version 1.0.0 @nosubgrouping */ NLOHMANN_BASIC_JSON_TPL_DECLARATION class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) { private: template friend struct detail::external_constructor; friend ::nlohmann::json_pointer; template friend class ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; template friend class ::nlohmann::detail::iter_impl; template friend class ::nlohmann::detail::binary_writer; template friend class ::nlohmann::detail::binary_reader; template friend class ::nlohmann::detail::json_sax_dom_parser; template friend class ::nlohmann::detail::json_sax_dom_callback_parser; friend class ::nlohmann::detail::exception; /// workaround type for MSVC using basic_json_t = NLOHMANN_BASIC_JSON_TPL; JSON_PRIVATE_UNLESS_TESTED: // convenience aliases for types residing in namespace detail; using lexer = ::nlohmann::detail::lexer_base; template static ::nlohmann::detail::parser parser( InputAdapterType adapter, detail::parser_callback_tcb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false ) { return ::nlohmann::detail::parser(std::move(adapter), std::move(cb), allow_exceptions, ignore_comments); } private: using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; template using internal_iterator = ::nlohmann::detail::internal_iterator; template using iter_impl = ::nlohmann::detail::iter_impl; template using iteration_proxy = ::nlohmann::detail::iteration_proxy; template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; template using output_adapter_t = ::nlohmann::detail::output_adapter_t; template using binary_reader = ::nlohmann::detail::binary_reader; template using binary_writer = ::nlohmann::detail::binary_writer; JSON_PRIVATE_UNLESS_TESTED: using serializer = ::nlohmann::detail::serializer; public: using value_t = detail::value_t; /// JSON Pointer, see @ref nlohmann::json_pointer using json_pointer = ::nlohmann::json_pointer; template using json_serializer = JSONSerializer; /// how to treat decoding errors using error_handler_t = detail::error_handler_t; /// how to treat CBOR tags using cbor_tag_handler_t = detail::cbor_tag_handler_t; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; using input_format_t = detail::input_format_t; /// SAX interface type, see @ref nlohmann::json_sax using json_sax_t = json_sax; //////////////// // exceptions // //////////////// /// @name exceptions /// Classes to implement user-defined exceptions. /// @{ using exception = detail::exception; using parse_error = detail::parse_error; using invalid_iterator = detail::invalid_iterator; using type_error = detail::type_error; using out_of_range = detail::out_of_range; using other_error = detail::other_error; /// @} ///////////////////// // container types // ///////////////////// /// @name container types /// The canonic container types to use @ref basic_json like any other STL /// container. /// @{ /// the type of elements in a basic_json container using value_type = basic_json; /// the type of an element reference using reference = value_type&; /// the type of an element const reference using const_reference = const value_type&; /// a type to represent differences between iterators using difference_type = std::ptrdiff_t; /// a type to represent container sizes using size_type = std::size_t; /// the allocator type using allocator_type = AllocatorType; /// the type of an element pointer using pointer = typename std::allocator_traits::pointer; /// the type of an element const pointer using const_pointer = typename std::allocator_traits::const_pointer; /// an iterator for a basic_json container using iterator = iter_impl; /// a const iterator for a basic_json container using const_iterator = iter_impl; /// a reverse iterator for a basic_json container using reverse_iterator = json_reverse_iterator; /// a const reverse iterator for a basic_json container using const_reverse_iterator = json_reverse_iterator; /// @} /// @brief returns the allocator associated with the container /// @sa https://json.nlohmann.me/api/basic_json/get_allocator/ static allocator_type get_allocator() { return allocator_type(); } /// @brief returns version information on the library /// @sa https://json.nlohmann.me/api/basic_json/meta/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json meta() { basic_json result; result["copyright"] = "(C) 2013-2022 Niels Lohmann"; result["name"] = "JSON for Modern C++"; result["url"] = "https://github.com/nlohmann/json"; result["version"]["string"] = std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + std::to_string(NLOHMANN_JSON_VERSION_PATCH); result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; #ifdef _WIN32 result["platform"] = "win32"; #elif defined __linux__ result["platform"] = "linux"; #elif defined __APPLE__ result["platform"] = "apple"; #elif defined __unix__ result["platform"] = "unix"; #else result["platform"] = "unknown"; #endif #if defined(__ICC) || defined(__INTEL_COMPILER) result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; #elif defined(__clang__) result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; #elif defined(__GNUC__) || defined(__GNUG__) result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; #elif defined(__HP_cc) || defined(__HP_aCC) result["compiler"] = "hp" #elif defined(__IBMCPP__) result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; #elif defined(_MSC_VER) result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; #elif defined(__PGI) result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; #elif defined(__SUNPRO_CC) result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; #else result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; #endif #ifdef __cplusplus result["compiler"]["c++"] = std::to_string(__cplusplus); #else result["compiler"]["c++"] = "unknown"; #endif return result; } /////////////////////////// // JSON value data types // /////////////////////////// /// @name JSON value data types /// The data types to store a JSON value. These types are derived from /// the template arguments passed to class @ref basic_json. /// @{ /// @brief object key comparator type /// @sa https://json.nlohmann.me/api/basic_json/object_comparator_t/ #if defined(JSON_HAS_CPP_14) // Use transparent comparator if possible, combined with perfect forwarding // on find() and count() calls prevents unnecessary string construction. using object_comparator_t = std::less<>; #else using object_comparator_t = std::less; #endif /// @brief a type for an object /// @sa https://json.nlohmann.me/api/basic_json/object_t/ using object_t = ObjectType>>; /// @brief a type for an array /// @sa https://json.nlohmann.me/api/basic_json/array_t/ using array_t = ArrayType>; /// @brief a type for a string /// @sa https://json.nlohmann.me/api/basic_json/string_t/ using string_t = StringType; /// @brief a type for a boolean /// @sa https://json.nlohmann.me/api/basic_json/boolean_t/ using boolean_t = BooleanType; /// @brief a type for a number (integer) /// @sa https://json.nlohmann.me/api/basic_json/number_integer_t/ using number_integer_t = NumberIntegerType; /// @brief a type for a number (unsigned) /// @sa https://json.nlohmann.me/api/basic_json/number_unsigned_t/ using number_unsigned_t = NumberUnsignedType; /// @brief a type for a number (floating-point) /// @sa https://json.nlohmann.me/api/basic_json/number_float_t/ using number_float_t = NumberFloatType; /// @brief a type for a packed binary type /// @sa https://json.nlohmann.me/api/basic_json/binary_t/ using binary_t = nlohmann::byte_container_with_subtype; /// @} private: /// helper for exception-safe object creation template JSON_HEDLEY_RETURNS_NON_NULL static T* create(Args&& ... args) { AllocatorType alloc; using AllocatorTraits = std::allocator_traits>; auto deleter = [&](T * obj) { AllocatorTraits::deallocate(alloc, obj, 1); }; std::unique_ptr obj(AllocatorTraits::allocate(alloc, 1), deleter); AllocatorTraits::construct(alloc, obj.get(), std::forward(args)...); JSON_ASSERT(obj != nullptr); return obj.release(); } //////////////////////// // JSON value storage // //////////////////////// JSON_PRIVATE_UNLESS_TESTED: /*! @brief a JSON value The actual storage for a JSON value of the @ref basic_json class. This union combines the different storage types for the JSON value types defined in @ref value_t. JSON type | value_t type | used type --------- | --------------- | ------------------------ object | object | pointer to @ref object_t array | array | pointer to @ref array_t string | string | pointer to @ref string_t boolean | boolean | @ref boolean_t number | number_integer | @ref number_integer_t number | number_unsigned | @ref number_unsigned_t number | number_float | @ref number_float_t binary | binary | pointer to @ref binary_t null | null | *no value is stored* @note Variable-length types (objects, arrays, and strings) are stored as pointers. The size of the union should not exceed 64 bits if the default value types are used. @since version 1.0.0 */ union json_value { /// object (stored with pointer to save storage) object_t* object; /// array (stored with pointer to save storage) array_t* array; /// string (stored with pointer to save storage) string_t* string; /// binary (stored with pointer to save storage) binary_t* binary; /// boolean boolean_t boolean; /// number (integer) number_integer_t number_integer; /// number (unsigned integer) number_unsigned_t number_unsigned; /// number (floating-point) number_float_t number_float; /// default constructor (for null values) json_value() = default; /// constructor for booleans json_value(boolean_t v) noexcept : boolean(v) {} /// constructor for numbers (integer) json_value(number_integer_t v) noexcept : number_integer(v) {} /// constructor for numbers (unsigned) json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} /// constructor for numbers (floating-point) json_value(number_float_t v) noexcept : number_float(v) {} /// constructor for empty values of a given type json_value(value_t t) { switch (t) { case value_t::object: { object = create(); break; } case value_t::array: { array = create(); break; } case value_t::string: { string = create(""); break; } case value_t::binary: { binary = create(); break; } case value_t::boolean: { boolean = static_cast(false); break; } case value_t::number_integer: { number_integer = static_cast(0); break; } case value_t::number_unsigned: { number_unsigned = static_cast(0); break; } case value_t::number_float: { number_float = static_cast(0.0); break; } case value_t::null: { object = nullptr; // silence warning, see #821 break; } case value_t::discarded: default: { object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.10.5", basic_json())); // LCOV_EXCL_LINE } break; } } } /// constructor for strings json_value(const string_t& value) : string(create(value)) {} /// constructor for rvalue strings json_value(string_t&& value) : string(create(std::move(value))) {} /// constructor for objects json_value(const object_t& value) : object(create(value)) {} /// constructor for rvalue objects json_value(object_t&& value) : object(create(std::move(value))) {} /// constructor for arrays json_value(const array_t& value) : array(create(value)) {} /// constructor for rvalue arrays json_value(array_t&& value) : array(create(std::move(value))) {} /// constructor for binary arrays json_value(const typename binary_t::container_type& value) : binary(create(value)) {} /// constructor for rvalue binary arrays json_value(typename binary_t::container_type&& value) : binary(create(std::move(value))) {} /// constructor for binary arrays (internal type) json_value(const binary_t& value) : binary(create(value)) {} /// constructor for rvalue binary arrays (internal type) json_value(binary_t&& value) : binary(create(std::move(value))) {} void destroy(value_t t) { if (t == value_t::array || t == value_t::object) { // flatten the current json_value to a heap-allocated stack std::vector stack; // move the top-level items to stack if (t == value_t::array) { stack.reserve(array->size()); std::move(array->begin(), array->end(), std::back_inserter(stack)); } else { stack.reserve(object->size()); for (auto&& it : *object) { stack.push_back(std::move(it.second)); } } while (!stack.empty()) { // move the last item to local variable to be processed basic_json current_item(std::move(stack.back())); stack.pop_back(); // if current_item is array/object, move // its children to the stack to be processed later if (current_item.is_array()) { std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), std::back_inserter(stack)); current_item.m_value.array->clear(); } else if (current_item.is_object()) { for (auto&& it : *current_item.m_value.object) { stack.push_back(std::move(it.second)); } current_item.m_value.object->clear(); } // it's now safe that current_item get destructed // since it doesn't have any children } } switch (t) { case value_t::object: { AllocatorType alloc; std::allocator_traits::destroy(alloc, object); std::allocator_traits::deallocate(alloc, object, 1); break; } case value_t::array: { AllocatorType alloc; std::allocator_traits::destroy(alloc, array); std::allocator_traits::deallocate(alloc, array, 1); break; } case value_t::string: { AllocatorType alloc; std::allocator_traits::destroy(alloc, string); std::allocator_traits::deallocate(alloc, string, 1); break; } case value_t::binary: { AllocatorType alloc; std::allocator_traits::destroy(alloc, binary); std::allocator_traits::deallocate(alloc, binary, 1); break; } case value_t::null: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::discarded: default: { break; } } } }; private: /*! @brief checks the class invariants This function asserts the class invariants. It needs to be called at the end of every constructor to make sure that created objects respect the invariant. Furthermore, it has to be called each time the type of a JSON value is changed, because the invariant expresses a relationship between @a m_type and @a m_value. Furthermore, the parent relation is checked for arrays and objects: If @a check_parents true and the value is an array or object, then the container's elements must have the current value as parent. @param[in] check_parents whether the parent relation should be checked. The value is true by default and should only be set to false during destruction of objects when the invariant does not need to hold. */ void assert_invariant(bool check_parents = true) const noexcept { JSON_ASSERT(m_type != value_t::object || m_value.object != nullptr); JSON_ASSERT(m_type != value_t::array || m_value.array != nullptr); JSON_ASSERT(m_type != value_t::string || m_value.string != nullptr); JSON_ASSERT(m_type != value_t::binary || m_value.binary != nullptr); #if JSON_DIAGNOSTICS JSON_TRY { // cppcheck-suppress assertWithSideEffect JSON_ASSERT(!check_parents || !is_structured() || std::all_of(begin(), end(), [this](const basic_json & j) { return j.m_parent == this; })); } JSON_CATCH(...) {} // LCOV_EXCL_LINE #endif static_cast(check_parents); } void set_parents() { #if JSON_DIAGNOSTICS switch (m_type) { case value_t::array: { for (auto& element : *m_value.array) { element.m_parent = this; } break; } case value_t::object: { for (auto& element : *m_value.object) { element.second.m_parent = this; } break; } case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: break; } #endif } iterator set_parents(iterator it, typename iterator::difference_type count_set_parents) { #if JSON_DIAGNOSTICS for (typename iterator::difference_type i = 0; i < count_set_parents; ++i) { (it + i)->m_parent = this; } #else static_cast(count_set_parents); #endif return it; } reference set_parent(reference j, std::size_t old_capacity = static_cast(-1)) { #if JSON_DIAGNOSTICS if (old_capacity != static_cast(-1)) { // see https://github.com/nlohmann/json/issues/2838 JSON_ASSERT(type() == value_t::array); if (JSON_HEDLEY_UNLIKELY(m_value.array->capacity() != old_capacity)) { // capacity has changed: update all parents set_parents(); return j; } } // ordered_json uses a vector internally, so pointers could have // been invalidated; see https://github.com/nlohmann/json/issues/2962 #ifdef JSON_HEDLEY_MSVC_VERSION #pragma warning(push ) #pragma warning(disable : 4127) // ignore warning to replace if with if constexpr #endif if (detail::is_ordered_map::value) { set_parents(); return j; } #ifdef JSON_HEDLEY_MSVC_VERSION #pragma warning( pop ) #endif j.m_parent = this; #else static_cast(j); static_cast(old_capacity); #endif return j; } public: ////////////////////////// // JSON parser callback // ////////////////////////// /// @brief parser event types /// @sa https://json.nlohmann.me/api/basic_json/parse_event_t/ using parse_event_t = detail::parse_event_t; /// @brief per-element parser callback type /// @sa https://json.nlohmann.me/api/basic_json/parser_callback_t/ using parser_callback_t = detail::parser_callback_t; ////////////////// // constructors // ////////////////// /// @name constructors and destructors /// Constructors of class @ref basic_json, copy/move constructor, copy /// assignment, static functions creating objects, and the destructor. /// @{ /// @brief create an empty value with a given type /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(const value_t v) : m_type(v), m_value(v) { assert_invariant(); } /// @brief create a null object /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(std::nullptr_t = nullptr) noexcept : basic_json(value_t::null) { assert_invariant(); } /// @brief create a JSON value from compatible types /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ template < typename CompatibleType, typename U = detail::uncvref_t, detail::enable_if_t < !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) JSONSerializer::to_json(std::declval(), std::forward(val)))) { JSONSerializer::to_json(*this, std::forward(val)); set_parents(); assert_invariant(); } /// @brief create a JSON value from an existing one /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ template < typename BasicJsonType, detail::enable_if_t < detail::is_basic_json::value&& !std::is_same::value, int > = 0 > basic_json(const BasicJsonType& val) { using other_boolean_t = typename BasicJsonType::boolean_t; using other_number_float_t = typename BasicJsonType::number_float_t; using other_number_integer_t = typename BasicJsonType::number_integer_t; using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; using other_string_t = typename BasicJsonType::string_t; using other_object_t = typename BasicJsonType::object_t; using other_array_t = typename BasicJsonType::array_t; using other_binary_t = typename BasicJsonType::binary_t; switch (val.type()) { case value_t::boolean: JSONSerializer::to_json(*this, val.template get()); break; case value_t::number_float: JSONSerializer::to_json(*this, val.template get()); break; case value_t::number_integer: JSONSerializer::to_json(*this, val.template get()); break; case value_t::number_unsigned: JSONSerializer::to_json(*this, val.template get()); break; case value_t::string: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::object: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::array: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::binary: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::null: *this = nullptr; break; case value_t::discarded: m_type = value_t::discarded; break; default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } set_parents(); assert_invariant(); } /// @brief create a container (array or object) from an initializer list /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(initializer_list_t init, bool type_deduction = true, value_t manual_type = value_t::array) { // check if each element is an array with two elements whose first // element is a string bool is_an_object = std::all_of(init.begin(), init.end(), [](const detail::json_ref& element_ref) { return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[0].is_string(); }); // adjust type if type deduction is not wanted if (!type_deduction) { // if array is wanted, do not create an object though possible if (manual_type == value_t::array) { is_an_object = false; } // if object is wanted but impossible, throw an exception if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) { JSON_THROW(type_error::create(301, "cannot create object from initializer list", basic_json())); } } if (is_an_object) { // the initializer list is a list of pairs -> create object m_type = value_t::object; m_value = value_t::object; for (auto& element_ref : init) { auto element = element_ref.moved_or_copied(); m_value.object->emplace( std::move(*((*element.m_value.array)[0].m_value.string)), std::move((*element.m_value.array)[1])); } } else { // the initializer list describes an array -> create array m_type = value_t::array; m_value.array = create(init.begin(), init.end()); } set_parents(); assert_invariant(); } /// @brief explicitly create a binary array (without subtype) /// @sa https://json.nlohmann.me/api/basic_json/binary/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(const typename binary_t::container_type& init) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = init; return res; } /// @brief explicitly create a binary array (with subtype) /// @sa https://json.nlohmann.me/api/basic_json/binary/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = binary_t(init, subtype); return res; } /// @brief explicitly create a binary array /// @sa https://json.nlohmann.me/api/basic_json/binary/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(typename binary_t::container_type&& init) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = std::move(init); return res; } /// @brief explicitly create a binary array (with subtype) /// @sa https://json.nlohmann.me/api/basic_json/binary/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = binary_t(std::move(init), subtype); return res; } /// @brief explicitly create an array from an initializer list /// @sa https://json.nlohmann.me/api/basic_json/array/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json array(initializer_list_t init = {}) { return basic_json(init, false, value_t::array); } /// @brief explicitly create an object from an initializer list /// @sa https://json.nlohmann.me/api/basic_json/object/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json object(initializer_list_t init = {}) { return basic_json(init, false, value_t::object); } /// @brief construct an array with count copies of given value /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(size_type cnt, const basic_json& val) : m_type(value_t::array) { m_value.array = create(cnt, val); set_parents(); assert_invariant(); } /// @brief construct a JSON container given an iterator range /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ template < class InputIT, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > basic_json(InputIT first, InputIT last) { JSON_ASSERT(first.m_object != nullptr); JSON_ASSERT(last.m_object != nullptr); // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", basic_json())); } // copy type from first iterator m_type = first.m_object->m_type; // check if iterator range is complete for primitive values switch (m_type) { case value_t::boolean: case value_t::number_float: case value_t::number_integer: case value_t::number_unsigned: case value_t::string: { if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range", *first.m_object)); } break; } case value_t::null: case value_t::object: case value_t::array: case value_t::binary: case value_t::discarded: default: break; } switch (m_type) { case value_t::number_integer: { m_value.number_integer = first.m_object->m_value.number_integer; break; } case value_t::number_unsigned: { m_value.number_unsigned = first.m_object->m_value.number_unsigned; break; } case value_t::number_float: { m_value.number_float = first.m_object->m_value.number_float; break; } case value_t::boolean: { m_value.boolean = first.m_object->m_value.boolean; break; } case value_t::string: { m_value = *first.m_object->m_value.string; break; } case value_t::object: { m_value.object = create(first.m_it.object_iterator, last.m_it.object_iterator); break; } case value_t::array: { m_value.array = create(first.m_it.array_iterator, last.m_it.array_iterator); break; } case value_t::binary: { m_value = *first.m_object->m_value.binary; break; } case value_t::null: case value_t::discarded: default: JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + std::string(first.m_object->type_name()), *first.m_object)); } set_parents(); assert_invariant(); } /////////////////////////////////////// // other constructors and destructor // /////////////////////////////////////// template, std::is_same>::value, int> = 0 > basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {} /// @brief copy constructor /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(const basic_json& other) : m_type(other.m_type) { // check of passed value is valid other.assert_invariant(); switch (m_type) { case value_t::object: { m_value = *other.m_value.object; break; } case value_t::array: { m_value = *other.m_value.array; break; } case value_t::string: { m_value = *other.m_value.string; break; } case value_t::boolean: { m_value = other.m_value.boolean; break; } case value_t::number_integer: { m_value = other.m_value.number_integer; break; } case value_t::number_unsigned: { m_value = other.m_value.number_unsigned; break; } case value_t::number_float: { m_value = other.m_value.number_float; break; } case value_t::binary: { m_value = *other.m_value.binary; break; } case value_t::null: case value_t::discarded: default: break; } set_parents(); assert_invariant(); } /// @brief move constructor /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(basic_json&& other) noexcept : m_type(std::move(other.m_type)), m_value(std::move(other.m_value)) { // check that passed value is valid other.assert_invariant(false); // invalidate payload other.m_type = value_t::null; other.m_value = {}; set_parents(); assert_invariant(); } /// @brief copy assignment /// @sa https://json.nlohmann.me/api/basic_json/operator=/ basic_json& operator=(basic_json other) noexcept ( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value ) { // check that passed value is valid other.assert_invariant(); using std::swap; swap(m_type, other.m_type); swap(m_value, other.m_value); set_parents(); assert_invariant(); return *this; } /// @brief destructor /// @sa https://json.nlohmann.me/api/basic_json/~basic_json/ ~basic_json() noexcept { assert_invariant(false); m_value.destroy(m_type); } /// @} public: /////////////////////// // object inspection // /////////////////////// /// @name object inspection /// Functions to inspect the type of a JSON value. /// @{ /// @brief serialization /// @sa https://json.nlohmann.me/api/basic_json/dump/ string_t dump(const int indent = -1, const char indent_char = ' ', const bool ensure_ascii = false, const error_handler_t error_handler = error_handler_t::strict) const { string_t result; serializer s(detail::output_adapter(result), indent_char, error_handler); if (indent >= 0) { s.dump(*this, true, ensure_ascii, static_cast(indent)); } else { s.dump(*this, false, ensure_ascii, 0); } return result; } /// @brief return the type of the JSON value (explicit) /// @sa https://json.nlohmann.me/api/basic_json/type/ constexpr value_t type() const noexcept { return m_type; } /// @brief return whether type is primitive /// @sa https://json.nlohmann.me/api/basic_json/is_primitive/ constexpr bool is_primitive() const noexcept { return is_null() || is_string() || is_boolean() || is_number() || is_binary(); } /// @brief return whether type is structured /// @sa https://json.nlohmann.me/api/basic_json/is_structured/ constexpr bool is_structured() const noexcept { return is_array() || is_object(); } /// @brief return whether value is null /// @sa https://json.nlohmann.me/api/basic_json/is_null/ constexpr bool is_null() const noexcept { return m_type == value_t::null; } /// @brief return whether value is a boolean /// @sa https://json.nlohmann.me/api/basic_json/is_boolean/ constexpr bool is_boolean() const noexcept { return m_type == value_t::boolean; } /// @brief return whether value is a number /// @sa https://json.nlohmann.me/api/basic_json/is_number/ constexpr bool is_number() const noexcept { return is_number_integer() || is_number_float(); } /// @brief return whether value is an integer number /// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/ constexpr bool is_number_integer() const noexcept { return m_type == value_t::number_integer || m_type == value_t::number_unsigned; } /// @brief return whether value is an unsigned integer number /// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/ constexpr bool is_number_unsigned() const noexcept { return m_type == value_t::number_unsigned; } /// @brief return whether value is a floating-point number /// @sa https://json.nlohmann.me/api/basic_json/is_number_float/ constexpr bool is_number_float() const noexcept { return m_type == value_t::number_float; } /// @brief return whether value is an object /// @sa https://json.nlohmann.me/api/basic_json/is_object/ constexpr bool is_object() const noexcept { return m_type == value_t::object; } /// @brief return whether value is an array /// @sa https://json.nlohmann.me/api/basic_json/is_array/ constexpr bool is_array() const noexcept { return m_type == value_t::array; } /// @brief return whether value is a string /// @sa https://json.nlohmann.me/api/basic_json/is_string/ constexpr bool is_string() const noexcept { return m_type == value_t::string; } /// @brief return whether value is a binary array /// @sa https://json.nlohmann.me/api/basic_json/is_binary/ constexpr bool is_binary() const noexcept { return m_type == value_t::binary; } /// @brief return whether value is discarded /// @sa https://json.nlohmann.me/api/basic_json/is_discarded/ constexpr bool is_discarded() const noexcept { return m_type == value_t::discarded; } /// @brief return the type of the JSON value (implicit) /// @sa https://json.nlohmann.me/api/basic_json/operator_value_t/ constexpr operator value_t() const noexcept { return m_type; } /// @} private: ////////////////// // value access // ////////////////// /// get a boolean (explicit) boolean_t get_impl(boolean_t* /*unused*/) const { if (JSON_HEDLEY_LIKELY(is_boolean())) { return m_value.boolean; } JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()), *this)); } /// get a pointer to the value (object) object_t* get_impl_ptr(object_t* /*unused*/) noexcept { return is_object() ? m_value.object : nullptr; } /// get a pointer to the value (object) constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept { return is_object() ? m_value.object : nullptr; } /// get a pointer to the value (array) array_t* get_impl_ptr(array_t* /*unused*/) noexcept { return is_array() ? m_value.array : nullptr; } /// get a pointer to the value (array) constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept { return is_array() ? m_value.array : nullptr; } /// get a pointer to the value (string) string_t* get_impl_ptr(string_t* /*unused*/) noexcept { return is_string() ? m_value.string : nullptr; } /// get a pointer to the value (string) constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept { return is_string() ? m_value.string : nullptr; } /// get a pointer to the value (boolean) boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept { return is_boolean() ? &m_value.boolean : nullptr; } /// get a pointer to the value (boolean) constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept { return is_boolean() ? &m_value.boolean : nullptr; } /// get a pointer to the value (integer number) number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept { return is_number_integer() ? &m_value.number_integer : nullptr; } /// get a pointer to the value (integer number) constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept { return is_number_integer() ? &m_value.number_integer : nullptr; } /// get a pointer to the value (unsigned number) number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } /// get a pointer to the value (unsigned number) constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } /// get a pointer to the value (floating-point number) number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept { return is_number_float() ? &m_value.number_float : nullptr; } /// get a pointer to the value (floating-point number) constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept { return is_number_float() ? &m_value.number_float : nullptr; } /// get a pointer to the value (binary) binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept { return is_binary() ? m_value.binary : nullptr; } /// get a pointer to the value (binary) constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept { return is_binary() ? m_value.binary : nullptr; } /*! @brief helper function to implement get_ref() This function helps to implement get_ref() without code duplication for const and non-const overloads @tparam ThisType will be deduced as `basic_json` or `const basic_json` @throw type_error.303 if ReferenceType does not match underlying value type of the current JSON */ template static ReferenceType get_ref_impl(ThisType& obj) { // delegate the call to get_ptr<>() auto* ptr = obj.template get_ptr::type>(); if (JSON_HEDLEY_LIKELY(ptr != nullptr)) { return *ptr; } JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()), obj)); } public: /// @name value access /// Direct access to the stored value of a JSON value. /// @{ /// @brief get a pointer value (implicit) /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/ template::value, int>::type = 0> auto get_ptr() noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) { // delegate the call to get_impl_ptr<>() return get_impl_ptr(static_cast(nullptr)); } /// @brief get a pointer value (implicit) /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/ template < typename PointerType, typename std::enable_if < std::is_pointer::value&& std::is_const::type>::value, int >::type = 0 > constexpr auto get_ptr() const noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) { // delegate the call to get_impl_ptr<>() const return get_impl_ptr(static_cast(nullptr)); } private: /*! @brief get a value (explicit) Explicit type conversion between the JSON value and a compatible value which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). The value is converted by calling the @ref json_serializer `from_json()` method. The function is equivalent to executing @code {.cpp} ValueType ret; JSONSerializer::from_json(*this, ret); return ret; @endcode This overloads is chosen if: - @a ValueType is not @ref basic_json, - @ref json_serializer has a `from_json()` method of the form `void from_json(const basic_json&, ValueType&)`, and - @ref json_serializer does not have a `from_json()` method of the form `ValueType from_json(const basic_json&)` @tparam ValueType the returned value type @return copy of the JSON value, converted to @a ValueType @throw what @ref json_serializer `from_json()` method throws @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ associative containers such as `std::unordered_map`.,get__ValueType_const} @since version 2.1.0 */ template < typename ValueType, detail::enable_if_t < detail::is_default_constructible::value&& detail::has_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept( JSONSerializer::from_json(std::declval(), std::declval()))) { auto ret = ValueType(); JSONSerializer::from_json(*this, ret); return ret; } /*! @brief get a value (explicit); special case Explicit type conversion between the JSON value and a compatible value which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). The value is converted by calling the @ref json_serializer `from_json()` method. The function is equivalent to executing @code {.cpp} return JSONSerializer::from_json(*this); @endcode This overloads is chosen if: - @a ValueType is not @ref basic_json and - @ref json_serializer has a `from_json()` method of the form `ValueType from_json(const basic_json&)` @note If @ref json_serializer has both overloads of `from_json()`, this one is chosen. @tparam ValueType the returned value type @return copy of the JSON value, converted to @a ValueType @throw what @ref json_serializer `from_json()` method throws @since version 2.1.0 */ template < typename ValueType, detail::enable_if_t < detail::has_non_default_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept( JSONSerializer::from_json(std::declval()))) { return JSONSerializer::from_json(*this); } /*! @brief get special-case overload This overloads converts the current @ref basic_json in a different @ref basic_json type @tparam BasicJsonType == @ref basic_json @return a copy of *this, converted into @a BasicJsonType @complexity Depending on the implementation of the called `from_json()` method. @since version 3.2.0 */ template < typename BasicJsonType, detail::enable_if_t < detail::is_basic_json::value, int > = 0 > BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const { return *this; } /*! @brief get special-case overload This overloads avoids a lot of template boilerplate, it can be seen as the identity method @tparam BasicJsonType == @ref basic_json @return a copy of *this @complexity Constant. @since version 2.1.0 */ template::value, int> = 0> basic_json get_impl(detail::priority_tag<3> /*unused*/) const { return *this; } /*! @brief get a pointer value (explicit) @copydoc get() */ template::value, int> = 0> constexpr auto get_impl(detail::priority_tag<4> /*unused*/) const noexcept -> decltype(std::declval().template get_ptr()) { // delegate the call to get_ptr return get_ptr(); } public: /*! @brief get a (pointer) value (explicit) Performs explicit type conversion between the JSON value and a compatible value if required. - If the requested type is a pointer to the internally stored JSON value that pointer is returned. No copies are made. - If the requested type is the current @ref basic_json, or a different @ref basic_json convertible from the current @ref basic_json. - Otherwise the value is converted by calling the @ref json_serializer `from_json()` method. @tparam ValueTypeCV the provided value type @tparam ValueType the returned value type @return copy of the JSON value, converted to @tparam ValueType if necessary @throw what @ref json_serializer `from_json()` method throws if conversion is required @since version 2.1.0 */ template < typename ValueTypeCV, typename ValueType = detail::uncvref_t> #if defined(JSON_HAS_CPP_14) constexpr #endif auto get() const noexcept( noexcept(std::declval().template get_impl(detail::priority_tag<4> {}))) -> decltype(std::declval().template get_impl(detail::priority_tag<4> {})) { // we cannot static_assert on ValueTypeCV being non-const, because // there is support for get(), which is why we // still need the uncvref static_assert(!std::is_reference::value, "get() cannot be used with reference types, you might want to use get_ref()"); return get_impl(detail::priority_tag<4> {}); } /*! @brief get a pointer value (explicit) Explicit pointer access to the internally stored JSON value. No copies are made. @warning The pointer becomes invalid if the underlying JSON object changes. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @complexity Constant. @liveexample{The example below shows how pointers to internal values of a JSON value can be requested. Note that no type conversions are made and a `nullptr` is returned if the value and the requested pointer type does not match.,get__PointerType} @sa see @ref get_ptr() for explicit pointer-member access @since version 1.0.0 */ template::value, int>::type = 0> auto get() noexcept -> decltype(std::declval().template get_ptr()) { // delegate the call to get_ptr return get_ptr(); } /// @brief get a value (explicit) /// @sa https://json.nlohmann.me/api/basic_json/get_to/ template < typename ValueType, detail::enable_if_t < !detail::is_basic_json::value&& detail::has_from_json::value, int > = 0 > ValueType & get_to(ValueType& v) const noexcept(noexcept( JSONSerializer::from_json(std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; } // specialization to allow calling get_to with a basic_json value // see https://github.com/nlohmann/json/issues/2175 template::value, int> = 0> ValueType & get_to(ValueType& v) const { v = *this; return v; } template < typename T, std::size_t N, typename Array = T (&)[N], // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) detail::enable_if_t < detail::has_from_json::value, int > = 0 > Array get_to(T (&v)[N]) const // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) noexcept(noexcept(JSONSerializer::from_json( std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; } /// @brief get a reference value (implicit) /// @sa https://json.nlohmann.me/api/basic_json/get_ref/ template::value, int>::type = 0> ReferenceType get_ref() { // delegate call to get_ref_impl return get_ref_impl(*this); } /// @brief get a reference value (implicit) /// @sa https://json.nlohmann.me/api/basic_json/get_ref/ template < typename ReferenceType, typename std::enable_if < std::is_reference::value&& std::is_const::type>::value, int >::type = 0 > ReferenceType get_ref() const { // delegate call to get_ref_impl return get_ref_impl(*this); } /*! @brief get a value (implicit) Implicit type conversion between the JSON value and a compatible value. The call is realized by calling @ref get() const. @tparam ValueType non-pointer type compatible to the JSON value, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for JSON arrays. The character type of @ref string_t as well as an initializer list of this type is excluded to avoid ambiguities as these types implicitly convert to `std::string`. @return copy of the JSON value, converted to type @a ValueType @throw type_error.302 in case passed type @a ValueType is incompatible to the JSON value type (e.g., the JSON value is of type boolean, but a string is requested); see example below @complexity Linear in the size of the JSON value. @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ associative containers such as `std::unordered_map`.,operator__ValueType} @since version 1.0.0 */ template < typename ValueType, typename std::enable_if < detail::conjunction < detail::negation>, detail::negation>>, detail::negation>, detail::negation>, detail::negation>>, #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) detail::negation>, #endif detail::is_detected_lazy >::value, int >::type = 0 > JSON_EXPLICIT operator ValueType() const { // delegate the call to get<>() const return get(); } /// @brief get a binary value /// @sa https://json.nlohmann.me/api/basic_json/get_binary/ binary_t& get_binary() { if (!is_binary()) { JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()), *this)); } return *get_ptr(); } /// @brief get a binary value /// @sa https://json.nlohmann.me/api/basic_json/get_binary/ const binary_t& get_binary() const { if (!is_binary()) { JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()), *this)); } return *get_ptr(); } /// @} //////////////////// // element access // //////////////////// /// @name element access /// Access to the JSON value. /// @{ /// @brief access specified array element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ reference at(size_type idx) { // at only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { JSON_TRY { return set_parent(m_value.array->at(idx)); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range", *this)); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()), *this)); } } /// @brief access specified array element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ const_reference at(size_type idx) const { // at only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { JSON_TRY { return m_value.array->at(idx); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range", *this)); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()), *this)); } } /// @brief access specified object element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ reference at(const typename object_t::key_type& key) { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_TRY { return set_parent(m_value.object->at(key)); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(403, "key '" + key + "' not found", *this)); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()), *this)); } } /// @brief access specified object element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ const_reference at(const typename object_t::key_type& key) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_TRY { return m_value.object->at(key); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(403, "key '" + key + "' not found", *this)); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()), *this)); } } /// @brief access specified array element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ reference operator[](size_type idx) { // implicitly convert null value to an empty array if (is_null()) { m_type = value_t::array; m_value.array = create(); assert_invariant(); } // operator[] only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { // fill up array with null values if given idx is outside range if (idx >= m_value.array->size()) { #if JSON_DIAGNOSTICS // remember array size & capacity before resizing const auto old_size = m_value.array->size(); const auto old_capacity = m_value.array->capacity(); #endif m_value.array->resize(idx + 1); #if JSON_DIAGNOSTICS if (JSON_HEDLEY_UNLIKELY(m_value.array->capacity() != old_capacity)) { // capacity has changed: update all parents set_parents(); } else { // set parent for values added above set_parents(begin() + static_cast(old_size), static_cast(idx + 1 - old_size)); } #endif assert_invariant(); } return m_value.array->operator[](idx); } JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()), *this)); } /// @brief access specified array element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ const_reference operator[](size_type idx) const { // const operator[] only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { return m_value.array->operator[](idx); } JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()), *this)); } /// @brief access specified object element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ reference operator[](const typename object_t::key_type& key) { // implicitly convert null value to an empty object if (is_null()) { m_type = value_t::object; m_value.object = create(); assert_invariant(); } // operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { return set_parent(m_value.object->operator[](key)); } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()), *this)); } /// @brief access specified object element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ const_reference operator[](const typename object_t::key_type& key) const { // const operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); return m_value.object->find(key)->second; } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()), *this)); } /// @brief access specified object element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ template JSON_HEDLEY_NON_NULL(2) reference operator[](T* key) { // implicitly convert null to object if (is_null()) { m_type = value_t::object; m_value = value_t::object; assert_invariant(); } // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { return set_parent(m_value.object->operator[](key)); } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()), *this)); } /// @brief access specified object element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ template JSON_HEDLEY_NON_NULL(2) const_reference operator[](T* key) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); return m_value.object->find(key)->second; } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()), *this)); } /// @brief access specified object element with default value /// @sa https://json.nlohmann.me/api/basic_json/value/ /// using std::is_convertible in a std::enable_if will fail when using explicit conversions template < class ValueType, typename std::enable_if < detail::is_getable::value && !std::is_same::value, int >::type = 0 > ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { // if key is found, return value and given default value otherwise const auto it = find(key); if (it != end()) { return it->template get(); } return default_value; } JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()), *this)); } /// @brief access specified object element with default value /// @sa https://json.nlohmann.me/api/basic_json/value/ /// overload for a default value of type const char* string_t value(const typename object_t::key_type& key, const char* default_value) const { return value(key, string_t(default_value)); } /// @brief access specified object element via JSON Pointer with default value /// @sa https://json.nlohmann.me/api/basic_json/value/ template::value, int>::type = 0> ValueType value(const json_pointer& ptr, const ValueType& default_value) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { // if pointer resolves a value, return it or use default value JSON_TRY { return ptr.get_checked(this).template get(); } JSON_INTERNAL_CATCH (out_of_range&) { return default_value; } } JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()), *this)); } /// @brief access specified object element via JSON Pointer with default value /// @sa https://json.nlohmann.me/api/basic_json/value/ /// overload for a default value of type const char* JSON_HEDLEY_NON_NULL(3) string_t value(const json_pointer& ptr, const char* default_value) const { return value(ptr, string_t(default_value)); } /// @brief access the first element /// @sa https://json.nlohmann.me/api/basic_json/front/ reference front() { return *begin(); } /// @brief access the first element /// @sa https://json.nlohmann.me/api/basic_json/front/ const_reference front() const { return *cbegin(); } /// @brief access the last element /// @sa https://json.nlohmann.me/api/basic_json/back/ reference back() { auto tmp = end(); --tmp; return *tmp; } /// @brief access the last element /// @sa https://json.nlohmann.me/api/basic_json/back/ const_reference back() const { auto tmp = cend(); --tmp; return *tmp; } /// @brief remove element given an iterator /// @sa https://json.nlohmann.me/api/basic_json/erase/ template < class IteratorType, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > IteratorType erase(IteratorType pos) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); } IteratorType result = end(); switch (m_type) { case value_t::boolean: case value_t::number_float: case value_t::number_integer: case value_t::number_unsigned: case value_t::string: case value_t::binary: { if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) { JSON_THROW(invalid_iterator::create(205, "iterator out of range", *this)); } if (is_string()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.string); std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } else if (is_binary()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.binary); std::allocator_traits::deallocate(alloc, m_value.binary, 1); m_value.binary = nullptr; } m_type = value_t::null; assert_invariant(); break; } case value_t::object: { result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); break; } case value_t::array: { result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); break; } case value_t::null: case value_t::discarded: default: JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()), *this)); } return result; } /// @brief remove elements given an iterator range /// @sa https://json.nlohmann.me/api/basic_json/erase/ template < class IteratorType, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > IteratorType erase(IteratorType first, IteratorType last) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) { JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", *this)); } IteratorType result = end(); switch (m_type) { case value_t::boolean: case value_t::number_float: case value_t::number_integer: case value_t::number_unsigned: case value_t::string: case value_t::binary: { if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range", *this)); } if (is_string()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.string); std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } else if (is_binary()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.binary); std::allocator_traits::deallocate(alloc, m_value.binary, 1); m_value.binary = nullptr; } m_type = value_t::null; assert_invariant(); break; } case value_t::object: { result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, last.m_it.object_iterator); break; } case value_t::array: { result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, last.m_it.array_iterator); break; } case value_t::null: case value_t::discarded: default: JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()), *this)); } return result; } /// @brief remove element from a JSON object given a key /// @sa https://json.nlohmann.me/api/basic_json/erase/ size_type erase(const typename object_t::key_type& key) { // this erase only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { return m_value.object->erase(key); } JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()), *this)); } /// @brief remove element from a JSON array given an index /// @sa https://json.nlohmann.me/api/basic_json/erase/ void erase(const size_type idx) { // this erase only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { if (JSON_HEDLEY_UNLIKELY(idx >= size())) { JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range", *this)); } m_value.array->erase(m_value.array->begin() + static_cast(idx)); } else { JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()), *this)); } } /// @} //////////// // lookup // //////////// /// @name lookup /// @{ /// @brief find an element in a JSON object /// @sa https://json.nlohmann.me/api/basic_json/find/ template iterator find(KeyT&& key) { auto result = end(); if (is_object()) { result.m_it.object_iterator = m_value.object->find(std::forward(key)); } return result; } /// @brief find an element in a JSON object /// @sa https://json.nlohmann.me/api/basic_json/find/ template const_iterator find(KeyT&& key) const { auto result = cend(); if (is_object()) { result.m_it.object_iterator = m_value.object->find(std::forward(key)); } return result; } /// @brief returns the number of occurrences of a key in a JSON object /// @sa https://json.nlohmann.me/api/basic_json/count/ template size_type count(KeyT&& key) const { // return 0 for all nonobject types return is_object() ? m_value.object->count(std::forward(key)) : 0; } /// @brief check the existence of an element in a JSON object /// @sa https://json.nlohmann.me/api/basic_json/contains/ template < typename KeyT, typename std::enable_if < !std::is_same::type, json_pointer>::value, int >::type = 0 > bool contains(KeyT && key) const { return is_object() && m_value.object->find(std::forward(key)) != m_value.object->end(); } /// @brief check the existence of an element in a JSON object given a JSON pointer /// @sa https://json.nlohmann.me/api/basic_json/contains/ bool contains(const json_pointer& ptr) const { return ptr.contains(this); } /// @} /////////////// // iterators // /////////////// /// @name iterators /// @{ /// @brief returns an iterator to the first element /// @sa https://json.nlohmann.me/api/basic_json/begin/ iterator begin() noexcept { iterator result(this); result.set_begin(); return result; } /// @brief returns an iterator to the first element /// @sa https://json.nlohmann.me/api/basic_json/begin/ const_iterator begin() const noexcept { return cbegin(); } /// @brief returns a const iterator to the first element /// @sa https://json.nlohmann.me/api/basic_json/cbegin/ const_iterator cbegin() const noexcept { const_iterator result(this); result.set_begin(); return result; } /// @brief returns an iterator to one past the last element /// @sa https://json.nlohmann.me/api/basic_json/end/ iterator end() noexcept { iterator result(this); result.set_end(); return result; } /// @brief returns an iterator to one past the last element /// @sa https://json.nlohmann.me/api/basic_json/end/ const_iterator end() const noexcept { return cend(); } /// @brief returns an iterator to one past the last element /// @sa https://json.nlohmann.me/api/basic_json/cend/ const_iterator cend() const noexcept { const_iterator result(this); result.set_end(); return result; } /// @brief returns an iterator to the reverse-beginning /// @sa https://json.nlohmann.me/api/basic_json/rbegin/ reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } /// @brief returns an iterator to the reverse-beginning /// @sa https://json.nlohmann.me/api/basic_json/rbegin/ const_reverse_iterator rbegin() const noexcept { return crbegin(); } /// @brief returns an iterator to the reverse-end /// @sa https://json.nlohmann.me/api/basic_json/rend/ reverse_iterator rend() noexcept { return reverse_iterator(begin()); } /// @brief returns an iterator to the reverse-end /// @sa https://json.nlohmann.me/api/basic_json/rend/ const_reverse_iterator rend() const noexcept { return crend(); } /// @brief returns a const reverse iterator to the last element /// @sa https://json.nlohmann.me/api/basic_json/crbegin/ const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); } /// @brief returns a const reverse iterator to one before the first /// @sa https://json.nlohmann.me/api/basic_json/crend/ const_reverse_iterator crend() const noexcept { return const_reverse_iterator(cbegin()); } public: /// @brief wrapper to access iterator member functions in range-based for /// @sa https://json.nlohmann.me/api/basic_json/items/ /// @deprecated This function is deprecated since 3.1.0 and will be removed in /// version 4.0.0 of the library. Please use @ref items() instead; /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(reference ref) noexcept { return ref.items(); } /// @brief wrapper to access iterator member functions in range-based for /// @sa https://json.nlohmann.me/api/basic_json/items/ /// @deprecated This function is deprecated since 3.1.0 and will be removed in /// version 4.0.0 of the library. Please use @ref items() instead; /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(const_reference ref) noexcept { return ref.items(); } /// @brief helper to access iterator member functions in range-based for /// @sa https://json.nlohmann.me/api/basic_json/items/ iteration_proxy items() noexcept { return iteration_proxy(*this); } /// @brief helper to access iterator member functions in range-based for /// @sa https://json.nlohmann.me/api/basic_json/items/ iteration_proxy items() const noexcept { return iteration_proxy(*this); } /// @} ////////////// // capacity // ////////////// /// @name capacity /// @{ /// @brief checks whether the container is empty. /// @sa https://json.nlohmann.me/api/basic_json/empty/ bool empty() const noexcept { switch (m_type) { case value_t::null: { // null values are empty return true; } case value_t::array: { // delegate call to array_t::empty() return m_value.array->empty(); } case value_t::object: { // delegate call to object_t::empty() return m_value.object->empty(); } case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { // all other types are nonempty return false; } } } /// @brief returns the number of elements /// @sa https://json.nlohmann.me/api/basic_json/size/ size_type size() const noexcept { switch (m_type) { case value_t::null: { // null values are empty return 0; } case value_t::array: { // delegate call to array_t::size() return m_value.array->size(); } case value_t::object: { // delegate call to object_t::size() return m_value.object->size(); } case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { // all other types have size 1 return 1; } } } /// @brief returns the maximum possible number of elements /// @sa https://json.nlohmann.me/api/basic_json/max_size/ size_type max_size() const noexcept { switch (m_type) { case value_t::array: { // delegate call to array_t::max_size() return m_value.array->max_size(); } case value_t::object: { // delegate call to object_t::max_size() return m_value.object->max_size(); } case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { // all other types have max_size() == size() return size(); } } } /// @} /////////////// // modifiers // /////////////// /// @name modifiers /// @{ /// @brief clears the contents /// @sa https://json.nlohmann.me/api/basic_json/clear/ void clear() noexcept { switch (m_type) { case value_t::number_integer: { m_value.number_integer = 0; break; } case value_t::number_unsigned: { m_value.number_unsigned = 0; break; } case value_t::number_float: { m_value.number_float = 0.0; break; } case value_t::boolean: { m_value.boolean = false; break; } case value_t::string: { m_value.string->clear(); break; } case value_t::binary: { m_value.binary->clear(); break; } case value_t::array: { m_value.array->clear(); break; } case value_t::object: { m_value.object->clear(); break; } case value_t::null: case value_t::discarded: default: break; } } /// @brief add an object to an array /// @sa https://json.nlohmann.me/api/basic_json/push_back/ void push_back(basic_json&& val) { // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()), *this)); } // transform null object into an array if (is_null()) { m_type = value_t::array; m_value = value_t::array; assert_invariant(); } // add element to array (move semantics) const auto old_capacity = m_value.array->capacity(); m_value.array->push_back(std::move(val)); set_parent(m_value.array->back(), old_capacity); // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor } /// @brief add an object to an array /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ reference operator+=(basic_json&& val) { push_back(std::move(val)); return *this; } /// @brief add an object to an array /// @sa https://json.nlohmann.me/api/basic_json/push_back/ void push_back(const basic_json& val) { // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()), *this)); } // transform null object into an array if (is_null()) { m_type = value_t::array; m_value = value_t::array; assert_invariant(); } // add element to array const auto old_capacity = m_value.array->capacity(); m_value.array->push_back(val); set_parent(m_value.array->back(), old_capacity); } /// @brief add an object to an array /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ reference operator+=(const basic_json& val) { push_back(val); return *this; } /// @brief add an object to an object /// @sa https://json.nlohmann.me/api/basic_json/push_back/ void push_back(const typename object_t::value_type& val) { // push_back only works for null objects or objects if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()), *this)); } // transform null object into an object if (is_null()) { m_type = value_t::object; m_value = value_t::object; assert_invariant(); } // add element to object auto res = m_value.object->insert(val); set_parent(res.first->second); } /// @brief add an object to an object /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ reference operator+=(const typename object_t::value_type& val) { push_back(val); return *this; } /// @brief add an object to an object /// @sa https://json.nlohmann.me/api/basic_json/push_back/ void push_back(initializer_list_t init) { if (is_object() && init.size() == 2 && (*init.begin())->is_string()) { basic_json&& key = init.begin()->moved_or_copied(); push_back(typename object_t::value_type( std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); } else { push_back(basic_json(init)); } } /// @brief add an object to an object /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ reference operator+=(initializer_list_t init) { push_back(init); return *this; } /// @brief add an object to an array /// @sa https://json.nlohmann.me/api/basic_json/emplace_back/ template reference emplace_back(Args&& ... args) { // emplace_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()), *this)); } // transform null object into an array if (is_null()) { m_type = value_t::array; m_value = value_t::array; assert_invariant(); } // add element to array (perfect forwarding) const auto old_capacity = m_value.array->capacity(); m_value.array->emplace_back(std::forward(args)...); return set_parent(m_value.array->back(), old_capacity); } /// @brief add an object to an object if key does not exist /// @sa https://json.nlohmann.me/api/basic_json/emplace/ template std::pair emplace(Args&& ... args) { // emplace only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()), *this)); } // transform null object into an object if (is_null()) { m_type = value_t::object; m_value = value_t::object; assert_invariant(); } // add element to array (perfect forwarding) auto res = m_value.object->emplace(std::forward(args)...); set_parent(res.first->second); // create result iterator and set iterator to the result of emplace auto it = begin(); it.m_it.object_iterator = res.first; // return pair of iterator and boolean return {it, res.second}; } /// Helper for insertion of an iterator /// @note: This uses std::distance to support GCC 4.8, /// see https://github.com/nlohmann/json/pull/1257 template iterator insert_iterator(const_iterator pos, Args&& ... args) { iterator result(this); JSON_ASSERT(m_value.array != nullptr); auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator); m_value.array->insert(pos.m_it.array_iterator, std::forward(args)...); result.m_it.array_iterator = m_value.array->begin() + insert_pos; // This could have been written as: // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); // but the return value of insert is missing in GCC 4.8, so it is written this way instead. set_parents(); return result; } /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ iterator insert(const_iterator pos, const basic_json& val) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); } // insert to array and return iterator return insert_iterator(pos, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); } /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ iterator insert(const_iterator pos, basic_json&& val) { return insert(pos, val); } /// @brief inserts copies of element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ iterator insert(const_iterator pos, size_type cnt, const basic_json& val) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); } // insert to array and return iterator return insert_iterator(pos, cnt, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); } /// @brief inserts range of elements into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ iterator insert(const_iterator pos, const_iterator first, const_iterator last) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit", *this)); } if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) { JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", *this)); } // insert to array and return iterator return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); } /// @brief inserts elements from initializer list into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ iterator insert(const_iterator pos, initializer_list_t ilist) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); } // insert to array and return iterator return insert_iterator(pos, ilist.begin(), ilist.end()); } /// @brief inserts range of elements into object /// @sa https://json.nlohmann.me/api/basic_json/insert/ void insert(const_iterator first, const_iterator last) { // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit", *this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", *this)); } m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); } /// @brief updates a JSON object from another object, overwriting existing keys /// @sa https://json.nlohmann.me/api/basic_json/update/ void update(const_reference j, bool merge_objects = false) { update(j.begin(), j.end(), merge_objects); } /// @brief updates a JSON object from another object, overwriting existing keys /// @sa https://json.nlohmann.me/api/basic_json/update/ void update(const_iterator first, const_iterator last, bool merge_objects = false) { // implicitly convert null value to an empty object if (is_null()) { m_type = value_t::object; m_value.object = create(); assert_invariant(); } if (JSON_HEDLEY_UNLIKELY(!is_object())) { JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()), *this)); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit", *this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(first.m_object->type_name()), *first.m_object)); } for (auto it = first; it != last; ++it) { if (merge_objects && it.value().is_object()) { auto it2 = m_value.object->find(it.key()); if (it2 != m_value.object->end()) { it2->second.update(it.value(), true); continue; } } m_value.object->operator[](it.key()) = it.value(); #if JSON_DIAGNOSTICS m_value.object->operator[](it.key()).m_parent = this; #endif } } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(reference other) noexcept ( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value ) { std::swap(m_type, other.m_type); std::swap(m_value, other.m_value); set_parents(); other.set_parents(); assert_invariant(); } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ friend void swap(reference left, reference right) noexcept ( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value ) { left.swap(right); } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(array_t& other) // NOLINT(bugprone-exception-escape) { // swap only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { std::swap(*(m_value.array), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); } } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(object_t& other) // NOLINT(bugprone-exception-escape) { // swap only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { std::swap(*(m_value.object), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); } } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(string_t& other) // NOLINT(bugprone-exception-escape) { // swap only works for strings if (JSON_HEDLEY_LIKELY(is_string())) { std::swap(*(m_value.string), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); } } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(binary_t& other) // NOLINT(bugprone-exception-escape) { // swap only works for strings if (JSON_HEDLEY_LIKELY(is_binary())) { std::swap(*(m_value.binary), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); } } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape) { // swap only works for strings if (JSON_HEDLEY_LIKELY(is_binary())) { std::swap(*(m_value.binary), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); } } /// @} public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// /// @name lexicographical comparison operators /// @{ /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ friend bool operator==(const_reference lhs, const_reference rhs) noexcept { #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif const auto lhs_type = lhs.type(); const auto rhs_type = rhs.type(); if (lhs_type == rhs_type) { switch (lhs_type) { case value_t::array: return *lhs.m_value.array == *rhs.m_value.array; case value_t::object: return *lhs.m_value.object == *rhs.m_value.object; case value_t::null: return true; case value_t::string: return *lhs.m_value.string == *rhs.m_value.string; case value_t::boolean: return lhs.m_value.boolean == rhs.m_value.boolean; case value_t::number_integer: return lhs.m_value.number_integer == rhs.m_value.number_integer; case value_t::number_unsigned: return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; case value_t::number_float: return lhs.m_value.number_float == rhs.m_value.number_float; case value_t::binary: return *lhs.m_value.binary == *rhs.m_value.binary; case value_t::discarded: default: return false; } } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) { return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) { return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); } return false; #ifdef __GNUC__ #pragma GCC diagnostic pop #endif } /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> friend bool operator==(const_reference lhs, ScalarType rhs) noexcept { return lhs == basic_json(rhs); } /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> friend bool operator==(ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) == rhs; } /// @brief comparison: not equal /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { return !(lhs == rhs); } /// @brief comparison: not equal /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept { return lhs != basic_json(rhs); } /// @brief comparison: not equal /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) != rhs; } /// @brief comparison: less than /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { const auto lhs_type = lhs.type(); const auto rhs_type = rhs.type(); if (lhs_type == rhs_type) { switch (lhs_type) { case value_t::array: // note parentheses are necessary, see // https://github.com/nlohmann/json/issues/1530 return (*lhs.m_value.array) < (*rhs.m_value.array); case value_t::object: return (*lhs.m_value.object) < (*rhs.m_value.object); case value_t::null: return false; case value_t::string: return (*lhs.m_value.string) < (*rhs.m_value.string); case value_t::boolean: return (lhs.m_value.boolean) < (rhs.m_value.boolean); case value_t::number_integer: return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); case value_t::number_unsigned: return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); case value_t::number_float: return (lhs.m_value.number_float) < (rhs.m_value.number_float); case value_t::binary: return (*lhs.m_value.binary) < (*rhs.m_value.binary); case value_t::discarded: default: return false; } } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) { return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) { return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; } // We only reach this line if we cannot compare values. In that case, // we compare types. Note we have to call the operator explicitly, // because MSVC has problems otherwise. return operator<(lhs_type, rhs_type); } /// @brief comparison: less than /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> friend bool operator<(const_reference lhs, ScalarType rhs) noexcept { return lhs < basic_json(rhs); } /// @brief comparison: less than /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> friend bool operator<(ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) < rhs; } /// @brief comparison: less than or equal /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { return !(rhs < lhs); } /// @brief comparison: less than or equal /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept { return lhs <= basic_json(rhs); } /// @brief comparison: less than or equal /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) <= rhs; } /// @brief comparison: greater than /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { return !(lhs <= rhs); } /// @brief comparison: greater than /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> friend bool operator>(const_reference lhs, ScalarType rhs) noexcept { return lhs > basic_json(rhs); } /// @brief comparison: greater than /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> friend bool operator>(ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) > rhs; } /// @brief comparison: greater than or equal /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { return !(lhs < rhs); } /// @brief comparison: greater than or equal /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept { return lhs >= basic_json(rhs); } /// @brief comparison: greater than or equal /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) >= rhs; } /// @} /////////////////// // serialization // /////////////////// /// @name serialization /// @{ #ifndef JSON_NO_IO /// @brief serialize to stream /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { // read width member and use it as indentation parameter if nonzero const bool pretty_print = o.width() > 0; const auto indentation = pretty_print ? o.width() : 0; // reset width to 0 for subsequent calls to this stream o.width(0); // do the actual serialization serializer s(detail::output_adapter(o), o.fill()); s.dump(j, pretty_print, false, static_cast(indentation)); return o; } /// @brief serialize to stream /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ /// @deprecated This function is deprecated since 3.0.0 and will be removed in /// version 4.0.0 of the library. Please use /// operator<<(std::ostream&, const basic_json&) instead; that is, /// replace calls like `j >> o;` with `o << j;`. JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) friend std::ostream& operator>>(const basic_json& j, std::ostream& o) { return o << j; } #endif // JSON_NO_IO /// @} ///////////////////// // deserialization // ///////////////////// /// @name deserialization /// @{ /// @brief deserialize from a compatible input /// @sa https://json.nlohmann.me/api/basic_json/parse/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(InputType&& i, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } /// @brief deserialize from a pair of character iterators /// @sa https://json.nlohmann.me/api/basic_json/parse/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(IteratorType first, IteratorType last, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } /// @brief check if the input is valid JSON /// @sa https://json.nlohmann.me/api/basic_json/accept/ template static bool accept(InputType&& i, const bool ignore_comments = false) { return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); } /// @brief check if the input is valid JSON /// @sa https://json.nlohmann.me/api/basic_json/accept/ template static bool accept(IteratorType first, IteratorType last, const bool ignore_comments = false) { return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) static bool accept(detail::span_input_adapter&& i, const bool ignore_comments = false) { return parser(i.get(), nullptr, false, ignore_comments).accept(true); } /// @brief generate SAX events /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ template JSON_HEDLEY_NON_NULL(2) static bool sax_parse(InputType&& i, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false) { auto ia = detail::input_adapter(std::forward(i)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } /// @brief generate SAX events /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ template JSON_HEDLEY_NON_NULL(3) static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false) { auto ia = detail::input_adapter(std::move(first), std::move(last)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } /// @brief generate SAX events /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ /// @deprecated This function is deprecated since 3.8.0 and will be removed in /// version 4.0.0 of the library. Please use /// sax_parse(ptr, ptr + len) instead. template JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) JSON_HEDLEY_NON_NULL(2) static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false) { auto ia = i.get(); return format == input_format_t::json // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } #ifndef JSON_NO_IO /// @brief deserialize from stream /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in /// version 4.0.0 of the library. Please use /// operator>>(std::istream&, basic_json&) instead; that is, /// replace calls like `j << i;` with `i >> j;`. JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) friend std::istream& operator<<(basic_json& j, std::istream& i) { return operator>>(i, j); } /// @brief deserialize from stream /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ friend std::istream& operator>>(std::istream& i, basic_json& j) { parser(detail::input_adapter(i)).parse(false, j); return i; } #endif // JSON_NO_IO /// @} /////////////////////////// // convenience functions // /////////////////////////// /// @brief return the type as string /// @sa https://json.nlohmann.me/api/basic_json/type_name/ JSON_HEDLEY_RETURNS_NON_NULL const char* type_name() const noexcept { switch (m_type) { case value_t::null: return "null"; case value_t::object: return "object"; case value_t::array: return "array"; case value_t::string: return "string"; case value_t::boolean: return "boolean"; case value_t::binary: return "binary"; case value_t::discarded: return "discarded"; case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: default: return "number"; } } JSON_PRIVATE_UNLESS_TESTED: ////////////////////// // member variables // ////////////////////// /// the type of the current element value_t m_type = value_t::null; /// the value of the current element json_value m_value = {}; #if JSON_DIAGNOSTICS /// a pointer to a parent value (for debugging purposes) basic_json* m_parent = nullptr; #endif ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// /// @name binary serialization/deserialization support /// @{ public: /// @brief create a CBOR serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ static std::vector to_cbor(const basic_json& j) { std::vector result; to_cbor(j, result); return result; } /// @brief create a CBOR serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ static void to_cbor(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_cbor(j); } /// @brief create a CBOR serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ static void to_cbor(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_cbor(j); } /// @brief create a MessagePack serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ static std::vector to_msgpack(const basic_json& j) { std::vector result; to_msgpack(j, result); return result; } /// @brief create a MessagePack serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ static void to_msgpack(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_msgpack(j); } /// @brief create a MessagePack serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ static void to_msgpack(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_msgpack(j); } /// @brief create a UBJSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ static std::vector to_ubjson(const basic_json& j, const bool use_size = false, const bool use_type = false) { std::vector result; to_ubjson(j, result, use_size, use_type); return result; } /// @brief create a UBJSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ static void to_ubjson(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false) { binary_writer(o).write_ubjson(j, use_size, use_type); } /// @brief create a UBJSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ static void to_ubjson(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false) { binary_writer(o).write_ubjson(j, use_size, use_type); } /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ static std::vector to_bson(const basic_json& j) { std::vector result; to_bson(j, result); return result; } /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ static void to_bson(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_bson(j); } /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ static void to_bson(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_bson(j); } /// @brief create a JSON value from an input in CBOR format /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_cbor(InputType&& i, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } /// @brief create a JSON value from an input in CBOR format /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_cbor(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) static basic_json from_cbor(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) static basic_json from_cbor(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } /// @brief create a JSON value from an input in MessagePack format /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(InputType&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @brief create a JSON value from an input in MessagePack format /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) static basic_json from_msgpack(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) { return from_msgpack(ptr, ptr + len, strict, allow_exceptions); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) static basic_json from_msgpack(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @brief create a JSON value from an input in UBJSON format /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(InputType&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @brief create a JSON value from an input in UBJSON format /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) static basic_json from_ubjson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) { return from_ubjson(ptr, ptr + len, strict, allow_exceptions); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) static basic_json from_ubjson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(InputType&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) static basic_json from_bson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) { return from_bson(ptr, ptr + len, strict, allow_exceptions); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) static basic_json from_bson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @} ////////////////////////// // JSON Pointer support // ////////////////////////// /// @name JSON Pointer functions /// @{ /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ reference operator[](const json_pointer& ptr) { return ptr.get_unchecked(this); } /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ const_reference operator[](const json_pointer& ptr) const { return ptr.get_unchecked(this); } /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/at/ reference at(const json_pointer& ptr) { return ptr.get_checked(this); } /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/at/ const_reference at(const json_pointer& ptr) const { return ptr.get_checked(this); } /// @brief return flattened JSON value /// @sa https://json.nlohmann.me/api/basic_json/flatten/ basic_json flatten() const { basic_json result(value_t::object); json_pointer::flatten("", *this, result); return result; } /// @brief unflatten a previously flattened JSON value /// @sa https://json.nlohmann.me/api/basic_json/unflatten/ basic_json unflatten() const { return json_pointer::unflatten(*this); } /// @} ////////////////////////// // JSON Patch functions // ////////////////////////// /// @name JSON Patch functions /// @{ /// @brief applies a JSON patch /// @sa https://json.nlohmann.me/api/basic_json/patch/ basic_json patch(const basic_json& json_patch) const { // make a working copy to apply the patch to basic_json result = *this; // the valid JSON Patch operations enum class patch_operations {add, remove, replace, move, copy, test, invalid}; const auto get_op = [](const std::string & op) { if (op == "add") { return patch_operations::add; } if (op == "remove") { return patch_operations::remove; } if (op == "replace") { return patch_operations::replace; } if (op == "move") { return patch_operations::move; } if (op == "copy") { return patch_operations::copy; } if (op == "test") { return patch_operations::test; } return patch_operations::invalid; }; // wrapper for "add" operation; add value at ptr const auto operation_add = [&result](json_pointer & ptr, basic_json val) { // adding to the root of the target document means replacing it if (ptr.empty()) { result = val; return; } // make sure the top element of the pointer exists json_pointer top_pointer = ptr.top(); if (top_pointer != ptr) { result.at(top_pointer); } // get reference to parent of JSON pointer ptr const auto last_path = ptr.back(); ptr.pop_back(); basic_json& parent = result[ptr]; switch (parent.m_type) { case value_t::null: case value_t::object: { // use operator[] to add value parent[last_path] = val; break; } case value_t::array: { if (last_path == "-") { // special case: append to back parent.push_back(val); } else { const auto idx = json_pointer::array_index(last_path); if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) { // avoid undefined behavior JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range", parent)); } // default case: insert add offset parent.insert(parent.begin() + static_cast(idx), val); } break; } // if there exists a parent it cannot be primitive case value_t::string: // LCOV_EXCL_LINE case value_t::boolean: // LCOV_EXCL_LINE case value_t::number_integer: // LCOV_EXCL_LINE case value_t::number_unsigned: // LCOV_EXCL_LINE case value_t::number_float: // LCOV_EXCL_LINE case value_t::binary: // LCOV_EXCL_LINE case value_t::discarded: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } }; // wrapper for "remove" operation; remove value at ptr const auto operation_remove = [this, &result](json_pointer & ptr) { // get reference to parent of JSON pointer ptr const auto last_path = ptr.back(); ptr.pop_back(); basic_json& parent = result.at(ptr); // remove child if (parent.is_object()) { // perform range check auto it = parent.find(last_path); if (JSON_HEDLEY_LIKELY(it != parent.end())) { parent.erase(it); } else { JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found", *this)); } } else if (parent.is_array()) { // note erase performs range check parent.erase(json_pointer::array_index(last_path)); } }; // type check: top level value must be an array if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) { JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", json_patch)); } // iterate and apply the operations for (const auto& val : json_patch) { // wrapper to get a value for an operation const auto get_value = [&val](const std::string & op, const std::string & member, bool string_type) -> basic_json & { // find value auto it = val.m_value.object->find(member); // context-sensitive error message const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; // check if desired value is present if (JSON_HEDLEY_UNLIKELY(it == val.m_value.object->end())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'", val)); } // check if result is of type string if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'", val)); } // no error: return value return it->second; }; // type check: every element of the array must be an object if (JSON_HEDLEY_UNLIKELY(!val.is_object())) { JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", val)); } // collect mandatory members const auto op = get_value("op", "op", true).template get(); const auto path = get_value(op, "path", true).template get(); json_pointer ptr(path); switch (get_op(op)) { case patch_operations::add: { operation_add(ptr, get_value("add", "value", false)); break; } case patch_operations::remove: { operation_remove(ptr); break; } case patch_operations::replace: { // the "path" location must exist - use at() result.at(ptr) = get_value("replace", "value", false); break; } case patch_operations::move: { const auto from_path = get_value("move", "from", true).template get(); json_pointer from_ptr(from_path); // the "from" location must exist - use at() basic_json v = result.at(from_ptr); // The move operation is functionally identical to a // "remove" operation on the "from" location, followed // immediately by an "add" operation at the target // location with the value that was just removed. operation_remove(from_ptr); operation_add(ptr, v); break; } case patch_operations::copy: { const auto from_path = get_value("copy", "from", true).template get(); const json_pointer from_ptr(from_path); // the "from" location must exist - use at() basic_json v = result.at(from_ptr); // The copy is functionally identical to an "add" // operation at the target location using the value // specified in the "from" member. operation_add(ptr, v); break; } case patch_operations::test: { bool success = false; JSON_TRY { // check if "value" matches the one at "path" // the "path" location must exist - use at() success = (result.at(ptr) == get_value("test", "value", false)); } JSON_INTERNAL_CATCH (out_of_range&) { // ignore out of range errors: success remains false } // throw an exception if test fails if (JSON_HEDLEY_UNLIKELY(!success)) { JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump(), val)); } break; } case patch_operations::invalid: default: { // op must be "add", "remove", "replace", "move", "copy", or // "test" JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid", val)); } } } return result; } /// @brief creates a diff as a JSON patch /// @sa https://json.nlohmann.me/api/basic_json/diff/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json diff(const basic_json& source, const basic_json& target, const std::string& path = "") { // the patch basic_json result(value_t::array); // if the values are the same, return empty patch if (source == target) { return result; } if (source.type() != target.type()) { // different types: replace value result.push_back( { {"op", "replace"}, {"path", path}, {"value", target} }); return result; } switch (source.type()) { case value_t::array: { // first pass: traverse common elements std::size_t i = 0; while (i < source.size() && i < target.size()) { // recursive call to compare array values at index i auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); ++i; } // We now reached the end of at least one array // in a second pass, traverse the remaining elements // remove my remaining elements const auto end_index = static_cast(result.size()); while (i < source.size()) { // add operations in reverse order to avoid invalid // indices result.insert(result.begin() + end_index, object( { {"op", "remove"}, {"path", path + "/" + std::to_string(i)} })); ++i; } // add other remaining elements while (i < target.size()) { result.push_back( { {"op", "add"}, {"path", path + "/-"}, {"value", target[i]} }); ++i; } break; } case value_t::object: { // first pass: traverse this object's elements for (auto it = source.cbegin(); it != source.cend(); ++it) { // escape the key name to be used in a JSON patch const auto path_key = path + "/" + detail::escape(it.key()); if (target.find(it.key()) != target.end()) { // recursive call to compare object values at key it auto temp_diff = diff(it.value(), target[it.key()], path_key); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); } else { // found a key that is not in o -> remove it result.push_back(object( { {"op", "remove"}, {"path", path_key} })); } } // second pass: traverse other object's elements for (auto it = target.cbegin(); it != target.cend(); ++it) { if (source.find(it.key()) == source.end()) { // found a key that is not in this -> add it const auto path_key = path + "/" + detail::escape(it.key()); result.push_back( { {"op", "add"}, {"path", path_key}, {"value", it.value()} }); } } break; } case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { // both primitive type: replace value result.push_back( { {"op", "replace"}, {"path", path}, {"value", target} }); break; } } return result; } /// @} //////////////////////////////// // JSON Merge Patch functions // //////////////////////////////// /// @name JSON Merge Patch functions /// @{ /// @brief applies a JSON Merge Patch /// @sa https://json.nlohmann.me/api/basic_json/merge_patch/ void merge_patch(const basic_json& apply_patch) { if (apply_patch.is_object()) { if (!is_object()) { *this = object(); } for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) { if (it.value().is_null()) { erase(it.key()); } else { operator[](it.key()).merge_patch(it.value()); } } } else { *this = apply_patch; } } /// @} }; /// @brief user-defined to_string function for JSON values /// @sa https://json.nlohmann.me/api/basic_json/to_string/ NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL& j) { return j.dump(); } } // namespace nlohmann /////////////////////// // nonmember support // /////////////////////// namespace std // NOLINT(cert-dcl58-cpp) { /// @brief hash value for JSON objects /// @sa https://json.nlohmann.me/api/basic_json/std_hash/ NLOHMANN_BASIC_JSON_TPL_DECLARATION struct hash { std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const { return nlohmann::detail::hash(j); } }; // specialization for std::less template<> struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', see https://github.com/nlohmann/json/pull/679 { /*! @brief compare two value_t enum values @since version 3.0.0 */ bool operator()(nlohmann::detail::value_t lhs, nlohmann::detail::value_t rhs) const noexcept { return nlohmann::detail::operator<(lhs, rhs); } }; // C++20 prohibit function specialization in the std namespace. #ifndef JSON_HAS_CPP_20 /// @brief exchanges the values of two JSON objects /// @sa https://json.nlohmann.me/api/basic_json/std_swap/ NLOHMANN_BASIC_JSON_TPL_DECLARATION inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name) is_nothrow_move_constructible::value&& // NOLINT(misc-redundant-expression) is_nothrow_move_assignable::value) { j1.swap(j2); } #endif } // namespace std /// @brief user-defined string literal for JSON values /// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json/ JSON_HEDLEY_NON_NULL(1) inline nlohmann::json operator "" _json(const char* s, std::size_t n) { return nlohmann::json::parse(s, s + n); } /// @brief user-defined string literal for JSON pointer /// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json_pointer/ JSON_HEDLEY_NON_NULL(1) inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) { return nlohmann::json::json_pointer(std::string(s, n)); } // #include // restore clang diagnostic settings #if defined(__clang__) #pragma clang diagnostic pop #endif // clean up #undef JSON_ASSERT #undef JSON_INTERNAL_CATCH #undef JSON_CATCH #undef JSON_THROW #undef JSON_TRY #undef JSON_PRIVATE_UNLESS_TESTED #undef JSON_HAS_CPP_11 #undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_17 #undef JSON_HAS_CPP_20 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef NLOHMANN_BASIC_JSON_TPL_DECLARATION #undef NLOHMANN_BASIC_JSON_TPL #undef JSON_EXPLICIT #undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL // #include #undef JSON_HEDLEY_ALWAYS_INLINE #undef JSON_HEDLEY_ARM_VERSION #undef JSON_HEDLEY_ARM_VERSION_CHECK #undef JSON_HEDLEY_ARRAY_PARAM #undef JSON_HEDLEY_ASSUME #undef JSON_HEDLEY_BEGIN_C_DECLS #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_BUILTIN #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_EXTENSION #undef JSON_HEDLEY_CLANG_HAS_FEATURE #undef JSON_HEDLEY_CLANG_HAS_WARNING #undef JSON_HEDLEY_COMPCERT_VERSION #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK #undef JSON_HEDLEY_CONCAT #undef JSON_HEDLEY_CONCAT3 #undef JSON_HEDLEY_CONCAT3_EX #undef JSON_HEDLEY_CONCAT_EX #undef JSON_HEDLEY_CONST #undef JSON_HEDLEY_CONSTEXPR #undef JSON_HEDLEY_CONST_CAST #undef JSON_HEDLEY_CPP_CAST #undef JSON_HEDLEY_CRAY_VERSION #undef JSON_HEDLEY_CRAY_VERSION_CHECK #undef JSON_HEDLEY_C_DECL #undef JSON_HEDLEY_DEPRECATED #undef JSON_HEDLEY_DEPRECATED_FOR #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION #undef JSON_HEDLEY_DIAGNOSTIC_POP #undef JSON_HEDLEY_DIAGNOSTIC_PUSH #undef JSON_HEDLEY_DMC_VERSION #undef JSON_HEDLEY_DMC_VERSION_CHECK #undef JSON_HEDLEY_EMPTY_BASES #undef JSON_HEDLEY_EMSCRIPTEN_VERSION #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK #undef JSON_HEDLEY_END_C_DECLS #undef JSON_HEDLEY_FLAGS #undef JSON_HEDLEY_FLAGS_CAST #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE #undef JSON_HEDLEY_GCC_HAS_BUILTIN #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_GCC_HAS_EXTENSION #undef JSON_HEDLEY_GCC_HAS_FEATURE #undef JSON_HEDLEY_GCC_HAS_WARNING #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK #undef JSON_HEDLEY_GCC_VERSION #undef JSON_HEDLEY_GCC_VERSION_CHECK #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE #undef JSON_HEDLEY_GNUC_HAS_BUILTIN #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_GNUC_HAS_EXTENSION #undef JSON_HEDLEY_GNUC_HAS_FEATURE #undef JSON_HEDLEY_GNUC_HAS_WARNING #undef JSON_HEDLEY_GNUC_VERSION #undef JSON_HEDLEY_GNUC_VERSION_CHECK #undef JSON_HEDLEY_HAS_ATTRIBUTE #undef JSON_HEDLEY_HAS_BUILTIN #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_HAS_EXTENSION #undef JSON_HEDLEY_HAS_FEATURE #undef JSON_HEDLEY_HAS_WARNING #undef JSON_HEDLEY_IAR_VERSION #undef JSON_HEDLEY_IAR_VERSION_CHECK #undef JSON_HEDLEY_IBM_VERSION #undef JSON_HEDLEY_IBM_VERSION_CHECK #undef JSON_HEDLEY_IMPORT #undef JSON_HEDLEY_INLINE #undef JSON_HEDLEY_INTEL_CL_VERSION #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK #undef JSON_HEDLEY_INTEL_VERSION #undef JSON_HEDLEY_INTEL_VERSION_CHECK #undef JSON_HEDLEY_IS_CONSTANT #undef JSON_HEDLEY_IS_CONSTEXPR_ #undef JSON_HEDLEY_LIKELY #undef JSON_HEDLEY_MALLOC #undef JSON_HEDLEY_MCST_LCC_VERSION #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK #undef JSON_HEDLEY_MESSAGE #undef JSON_HEDLEY_MSVC_VERSION #undef JSON_HEDLEY_MSVC_VERSION_CHECK #undef JSON_HEDLEY_NEVER_INLINE #undef JSON_HEDLEY_NON_NULL #undef JSON_HEDLEY_NO_ESCAPE #undef JSON_HEDLEY_NO_RETURN #undef JSON_HEDLEY_NO_THROW #undef JSON_HEDLEY_NULL #undef JSON_HEDLEY_PELLES_VERSION #undef JSON_HEDLEY_PELLES_VERSION_CHECK #undef JSON_HEDLEY_PGI_VERSION #undef JSON_HEDLEY_PGI_VERSION_CHECK #undef JSON_HEDLEY_PREDICT #undef JSON_HEDLEY_PRINTF_FORMAT #undef JSON_HEDLEY_PRIVATE #undef JSON_HEDLEY_PUBLIC #undef JSON_HEDLEY_PURE #undef JSON_HEDLEY_REINTERPRET_CAST #undef JSON_HEDLEY_REQUIRE #undef JSON_HEDLEY_REQUIRE_CONSTEXPR #undef JSON_HEDLEY_REQUIRE_MSG #undef JSON_HEDLEY_RESTRICT #undef JSON_HEDLEY_RETURNS_NON_NULL #undef JSON_HEDLEY_SENTINEL #undef JSON_HEDLEY_STATIC_ASSERT #undef JSON_HEDLEY_STATIC_CAST #undef JSON_HEDLEY_STRINGIFY #undef JSON_HEDLEY_STRINGIFY_EX #undef JSON_HEDLEY_SUNPRO_VERSION #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK #undef JSON_HEDLEY_TINYC_VERSION #undef JSON_HEDLEY_TINYC_VERSION_CHECK #undef JSON_HEDLEY_TI_ARMCL_VERSION #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK #undef JSON_HEDLEY_TI_CL2000_VERSION #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK #undef JSON_HEDLEY_TI_CL430_VERSION #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK #undef JSON_HEDLEY_TI_CL6X_VERSION #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK #undef JSON_HEDLEY_TI_CL7X_VERSION #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK #undef JSON_HEDLEY_TI_CLPRU_VERSION #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK #undef JSON_HEDLEY_TI_VERSION #undef JSON_HEDLEY_TI_VERSION_CHECK #undef JSON_HEDLEY_UNAVAILABLE #undef JSON_HEDLEY_UNLIKELY #undef JSON_HEDLEY_UNPREDICTABLE #undef JSON_HEDLEY_UNREACHABLE #undef JSON_HEDLEY_UNREACHABLE_RETURN #undef JSON_HEDLEY_VERSION #undef JSON_HEDLEY_VERSION_DECODE_MAJOR #undef JSON_HEDLEY_VERSION_DECODE_MINOR #undef JSON_HEDLEY_VERSION_DECODE_REVISION #undef JSON_HEDLEY_VERSION_ENCODE #undef JSON_HEDLEY_WARNING #undef JSON_HEDLEY_WARN_UNUSED_RESULT #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG #undef JSON_HEDLEY_FALL_THROUGH #endif // INCLUDE_NLOHMANN_JSON_HPP_ upstream-fastnetmon/src/fastnetmon_networks.hpp0000664000175000017500000001354315060514305020277 0ustar meme#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; }; upstream-fastnetmon/src/plugin_runner.cpp0000664000175000017500000001047015060514305017043 0ustar meme#include #include #include #include #include #include #include #include #include #include #include #include "fast_library.h" #include "fastnetmon_types.h" #include "libpatricia/patricia.hpp" #include "netflow_plugin/netflow_collector.h" #ifdef ENABLE_PCAP #include "pcap_plugin/pcap_collector.h" #endif #include "sflow_plugin/sflow_collector.h" #ifdef FASTNETMON_ENABLE_AFPACKET #include "afpacket_plugin/afpacket_collector.h" #endif #ifdef NETMAP_PLUGIN #include "netmap_plugin/netmap_collector.h" #endif // 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" #include using namespace std; uint64_t total_unparsed_packets = 0; std::string log_file_path = "/tmp/fastnetmon_plugin_tester.log"; log4cpp::Category& logger = log4cpp::Category::getRoot(); // #define DO_SUBNET_LOOKUP #ifdef DO_SUBNET_LOOKUP patricia_tree_t* lookup_tree; #endif // Global map with parsed config file std::map configuration_map; void init_logging() { log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("%d [%p] %m%n"); log4cpp::Appender* appender = new log4cpp::FileAppender("default", log_file_path); appender->setLayout(layout); logger.setPriority(log4cpp::Priority::INFO); logger.addAppender(appender); logger.info("Logger initialized"); } void process_packet(simple_packet& current_packet) { std::cout << print_simple_packet(current_packet); #ifdef DO_SUBNET_LOOKUP unsigned long subnet = 0; unsigned int subnet_cidr_mask = 0; direction packet_direction = get_packet_direction(lookup_tree, current_packet.src_ip, current_packet.dst_ip, subnet, subnet_cidr_mask); std::cout << "direction: " << get_direction_name(packet_direction) << std::endl; #endif } // Copy & paste from fastnetmon.cpp std::vector read_file_to_vector(std::string file_name) { std::vector data; std::string line; std::ifstream reading_file; reading_file.open(file_name.c_str(), std::ifstream::in); if (reading_file.is_open()) { while (getline(reading_file, line)) { data.push_back(line); } } else { logger << log4cpp::Priority::ERROR << "Can't open file: " << file_name; } return data; } int main(int argc, char* argv[]) { init_logging(); if (argc < 2) { std::cout << "Please specify sflow, netflow, raw, afpacket as param" << std::endl; return 1; } #ifdef DO_SUBNET_LOOKUP lookup_tree = New_Patricia(32); std::vector network_list_from_config = read_file_to_vector("/etc/networks_list"); for (std::vector::iterator ii = network_list_from_config.begin(); ii != network_list_from_config.end(); ++ii) { std::string network_address_in_cidr_form = *ii; make_and_lookup(lookup_tree, network_address_in_cidr_form.c_str()); } #endif // Required by Netmap plugin // We use fake interface name here because netmap could make server unreachable :) configuration_map["interfaces"] = "ethXXX"; if (strstr(argv[1], "sflow") != NULL) { std::cout << "Starting sflow" << std::endl; start_sflow_collection(process_packet); } else if (strstr(argv[1], "netflow") != NULL) { std::cout << "Starting netflow" << std::endl; start_netflow_collection(process_packet); } else if (strstr(argv[1], "pcap") != NULL) { std::cout << "Starting pcap" << std::endl; start_pcap_collection(process_packet); } else if (strstr(argv[1], "afpacket") != NULL) { #ifdef FASTNETMON_ENABLE_AFPACKET std::cout << "Starting afpacket" << std::endl; start_afpacket_collection(process_packet); #else printf("AF_PACKET is not supported here"); #endif } else if (strstr(argv[1], "netmap") != NULL) { std::cout << "Starting netmap" << std::endl; start_netmap_collection(process_packet); } else { std::cout << "Bad plugin name!" << std::endl; } } upstream-fastnetmon/src/fastnetmon_internal_api.proto0000664000175000017500000001253215060514305021441 0ustar memesyntax = "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; } upstream-fastnetmon/src/example_plugin/0000755000175000017500000000000015060514305016455 5ustar memeupstream-fastnetmon/src/example_plugin/example_collector.hpp0000664000175000017500000000032015060514305022664 0ustar meme#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 upstream-fastnetmon/src/example_plugin/example_collector.cpp0000664000175000017500000000351215060514305022665 0ustar meme#include "../all_logcpp_libraries.hpp" // For config map operations #include #include #include "../iana_ip_protocols.hpp" #include "example_collector.hpp" // Get log4cpp logger from main program extern log4cpp::Category& logger; // Global configuration map extern std::map configuration_map; // This variable name should be uniq for every plugin! process_packet_pointer example_process_func_ptr = NULL; void start_example_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "Example plugin started"; example_process_func_ptr = func_ptr; std::string example_plugin_config_param = ""; if (configuration_map.count("some_plugin_param_from_global_config") != 0) { example_plugin_config_param = configuration_map["some_plugin_param_from_global_config"]; } // We should fill this structure for passing to FastNetMon simple_packet_t current_packet; current_packet.src_ip = 0; current_packet.dst_ip = 0; current_packet.ts.tv_sec = 0; current_packet.ts.tv_usec = 0; current_packet.flags = 0; // There we store packet length or total length of aggregated stream current_packet.length = 128; // Number of received packets, it's not equal to 1 only for aggregated data like netflow current_packet.number_of_packets = 1; // If your data sampled current_packet.sample_ratio = 1; /* ICMP */ current_packet.protocol = IpProtocolNumberICMP; /* TCP */ current_packet.protocol = IpProtocolNumberTCP; current_packet.source_port = 0; current_packet.destination_port = 0; /* UDP */ current_packet.protocol = IpProtocolNumberUDP; current_packet.source_port = 0; current_packet.destination_port = 0; example_process_func_ptr(current_packet); } upstream-fastnetmon/src/networks_list0000644000175000017500000000000014230006537016265 0ustar memeupstream-fastnetmon/src/fastnetmon.cpp0000664000175000017500000022616515060514305016344 0ustar meme/* 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); } upstream-fastnetmon/src/fastnetmon_pcap_format.cpp0000664000175000017500000000554715060514305020716 0ustar meme#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; } upstream-fastnetmon/src/fastnetmon_actions.hpp0000664000175000017500000000034615060514305020060 0ustar meme#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; upstream-fastnetmon/src/bgp_protocol_flow_spec.cpp0000664000175000017500000026175415060514305020723 0ustar meme#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_maximum_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_maximum_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_maximum_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_maximum_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_maximum_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) // ; // Decode IPv4 prefixes if (*local_data_ptr == FLOW_SPEC_ENTITY_DESTINATION_PREFIX or *local_data_ptr == FLOW_SPEC_ENTITY_SOURCE_PREFIX) { // Well, we've found BGP encoded subnet. Let's parse it! if (*local_data_ptr == 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; } if (*local_data_ptr == 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; } // We need least two bytes here (type + prefix length) if (packet_end - local_data_ptr < 2) { logger << log4cpp::Priority::WARN << "Too short packet. We need more data for bgp encoded subnet"; return false; } uint8_t prefix_bit_length = *(local_data_ptr + 1); uint32_t prefix_byte_length = how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_bit_length); uint32_t full_size_of_bgp_encoded_subnet = 2 + prefix_byte_length; if (packet_end - local_data_ptr < full_size_of_bgp_encoded_subnet) { logger << log4cpp::Priority::WARN << "We haven't enough data for this prefix with length " << prefix_bit_length; return false; } subnet_cidr_mask_t extracted_prefix; bool decode_nlri_result = decode_bgp_subnet_encoding_ipv4_raw(local_data_ptr + 1, extracted_prefix); if (!decode_nlri_result) { logger << log4cpp::Priority::WARN << "Could not decode FLOW_SPEC_ENTITY_DESTINATION_PREFIX"; return false; } if (*local_data_ptr == FLOW_SPEC_ENTITY_DESTINATION_PREFIX) { flow_spec_rule.set_destination_subnet_ipv4(extracted_prefix); } if (*local_data_ptr == FLOW_SPEC_ENTITY_SOURCE_PREFIX) { flow_spec_rule.set_source_subnet_ipv4(extracted_prefix); } // Reduce packet length // 1 means length of type local_data_ptr += full_size_of_bgp_encoded_subnet; } else if (*local_data_ptr == FLOW_SPEC_ENTITY_PORT or *local_data_ptr == FLOW_SPEC_ENTITY_DESTINATION_PORT or *local_data_ptr == FLOW_SPEC_ENTITY_SOURCE_PORT or *local_data_ptr == FLOW_SPEC_ENTITY_IP_PROTOCOL or *local_data_ptr == FLOW_SPEC_ENTITY_PACKET_LENGTH or *local_data_ptr == FLOW_SPEC_ENTITY_FRAGMENT or *local_data_ptr == FLOW_SPEC_ENTITY_TCP_FLAGS) { uint8_t current_type = *local_data_ptr; // Skip type field local_data_ptr++; // Different type of port's if (current_type == FLOW_SPEC_ENTITY_PORT) { logger << log4cpp::Priority::WARN << "We do not support common ports"; return false; } 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); // This one is considered non fatal, we try to do our best in parsing it 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"; } for (auto extracted_item : scanned_items) { if (current_type == FLOW_SPEC_ENTITY_TCP_FLAGS) { 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); } } else if (current_type == FLOW_SPEC_ENTITY_FRAGMENT) { 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); } } if (current_type == FLOW_SPEC_ENTITY_SOURCE_PORT) { flow_spec_rule.add_source_port(extracted_item.two_byte_value); } if (current_type == FLOW_SPEC_ENTITY_DESTINATION_PORT) { flow_spec_rule.add_destination_port(extracted_item.two_byte_value); } if (current_type == FLOW_SPEC_ENTITY_PACKET_LENGTH) { flow_spec_rule.add_packet_length(extracted_item.two_byte_value); } if (current_type == FLOW_SPEC_ENTITY_IP_PROTOCOL) { // 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 { 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 haven't parsed 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; } upstream-fastnetmon/src/ip_lookup_tree.hpp0000664000175000017500000001156515060514305017207 0ustar meme#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; }; upstream-fastnetmon/src/simple_packet_parser_ng.hpp0000664000175000017500000000270315060514305021041 0ustar meme#pragma once #include "fastnetmon_simple_packet.hpp" #include "network_data_structures.hpp" // This class is used to alter parser behaviour class parser_options_t { public: // Enables logic to unpack GRE bool unpack_gre = false; bool read_packet_length_from_ip_header = false; // Enables logic to unpack GTPv1 bool unpack_gtp_v1 = false; }; network_data_stuctures::parser_code_t parse_raw_packet_to_simple_packet_full_ng(const uint8_t* pointer, int length_before_sampling, int captured_length, simple_packet_t& packet, const parser_options_t& parser_options); network_data_stuctures::parser_code_t parse_raw_ipv4_packet_to_simple_packet_full_ng(const uint8_t* pointer, int length_before_sampling, int captured_length, simple_packet_t& packet, const parser_options_t& parser_options); upstream-fastnetmon/src/sflow_plugin/0000755000175000017500000000000015060514305016154 5ustar memeupstream-fastnetmon/src/sflow_plugin/sflow_collector.cpp0000664000175000017500000007747215060514305022103 0ustar meme#include #include #include #include #include "../libsflow/libsflow.hpp" #include "sflow_collector.hpp" #ifdef _WIN32 #include #include // sockaddr_in6 #include // socklen_t #else #include #include #include #include #endif #include "../fast_library.hpp" #include "../fastnetmon_plugin.hpp" #include "../all_logcpp_libraries.hpp" extern log4cpp::Category& logger; #include "../simple_packet_parser_ng.hpp" #include #include "../fastnetmon_configuration_scheme.hpp" extern fastnetmon_configuration_t fastnetmon_global_configuration; std::string raw_udp_packets_received_desc = "Number of raw packets received without any errors"; uint64_t raw_udp_packets_received = 0; std::string udp_receive_errors_desc = "Number of failed receives"; uint64_t udp_receive_errors = 0; std::string udp_receive_eagain_desc = "Number of eagains"; uint64_t udp_receive_eagain = 0; std::string plugin_name = "sflow"; std::string plugin_log_prefix = plugin_name + ": "; std::string sflow_total_packets_desc = "Total number of received UDP sFlow packets"; uint64_t sflow_total_packets = 0; std::string sflow_bad_packets_desc = "Incorrectly crafted sFlow packets"; uint64_t sflow_bad_packets = 0; std::string sflow_flow_samples_desc = "Number of flow samples, i.e. with packet headers"; uint64_t sflow_flow_samples = 0; std::string sflow_bad_flow_samples_desc = "Number of broken flow samples"; uint64_t sflow_bad_flow_samples = 0; std::string sflow_with_padding_at_the_end_of_packet_desc = "Number of packets where we have padding at the end of packet"; uint64_t sflow_with_padding_at_the_end_of_packet = 0; std::string sflow_padding_flow_sample_desc = "Number of packets with padding inside flow sample"; uint64_t sflow_padding_flow_sample = 0; std::string sflow_parse_error_nested_header_desc = "Number of packet headers from flow samples which could not be decoded correctly"; uint64_t sflow_parse_error_nested_header = 0; std::string sflow_counter_sample_desc = "Number of counter samples, i.e. with port counters"; uint64_t sflow_counter_sample = 0; std::string sflow_expanded_counter_sample_desc = "Number of expanded counter samples, i.e. with port counters"; uint64_t sflow_expanded_counter_sample = 0; std::string sflow_generic_interface_counter_sample_desc = "Number of counter samples with generic interface counter information"; uint64_t sflow_generic_interface_counter_sample = 0; std::string sflow_raw_packet_headers_total_desc = "Number of packet headers from flow samples"; uint64_t sflow_raw_packet_headers_total = 0; std::string sflow_extended_router_data_records_desc = "Number of records with extended information from routers"; uint64_t sflow_extended_router_data_records = 0; std::string sflow_extended_switch_data_records_desc = "Number of samples with switch data"; uint64_t sflow_extended_switch_data_records = 0; std::string sflow_extended_gateway_data_records_desc = "Number of samples with gateway data"; uint64_t sflow_extended_gateway_data_records = 0; std::string sflow_unknown_header_protocol_desc = "Number of packets for unknown header protocol"; uint64_t sflow_unknown_header_protocol = 0; std::string sflow_ipv4_header_protocol_desc = "Number of samples with IPv4 packet headers"; uint64_t sflow_ipv4_header_protocol = 0; std::string sflow_ipv6_header_protocol_desc = "Number of samples with IPv6 packet headers"; uint64_t sflow_ipv6_header_protocol = 0; std::string sflow_packets_discarded_desc = "Number of packets discarded by device"; uint64_t sflow_packets_discarded = 0; std::vector get_sflow_stats() { std::vector counters; counters.push_back(system_counter_t("sflow_raw_udp_packets_received", raw_udp_packets_received, metric_type_t::counter, raw_udp_packets_received_desc)); counters.push_back(system_counter_t("sflow_udp_receive_errors", udp_receive_errors, metric_type_t::counter, udp_receive_errors_desc)); counters.push_back(system_counter_t("sflow_udp_receive_eagain", udp_receive_eagain, metric_type_t::counter, udp_receive_eagain_desc)); counters.push_back(system_counter_t("sflow_total_packets", sflow_total_packets, metric_type_t::counter, sflow_total_packets_desc)); counters.push_back(system_counter_t("sflow_bad_packets", sflow_bad_packets, metric_type_t::counter, sflow_bad_packets_desc)); counters.push_back(system_counter_t("sflow_flow_samples", sflow_flow_samples, metric_type_t::counter, sflow_flow_samples_desc)); counters.push_back(system_counter_t("sflow_bad_flow_samples", sflow_bad_flow_samples, metric_type_t::counter, sflow_bad_flow_samples_desc)); counters.push_back(system_counter_t("sflow_padding_flow_sample", sflow_padding_flow_sample, metric_type_t::counter, sflow_padding_flow_sample_desc)); counters.push_back(system_counter_t("sflow_with_padding_at_the_end_of_packet", sflow_with_padding_at_the_end_of_packet, metric_type_t::counter, sflow_with_padding_at_the_end_of_packet_desc)); counters.push_back(system_counter_t("sflow_parse_error_nested_header", sflow_parse_error_nested_header, metric_type_t::counter, sflow_parse_error_nested_header_desc)); counters.push_back(system_counter_t("sflow_counter_sample", sflow_counter_sample, metric_type_t::counter, sflow_counter_sample_desc)); counters.push_back(system_counter_t("sflow_expanded_counter_sample", sflow_expanded_counter_sample, metric_type_t::counter, sflow_expanded_counter_sample_desc)); counters.push_back(system_counter_t("sflow_generic_interface_counter_sample", sflow_generic_interface_counter_sample, metric_type_t::counter, sflow_generic_interface_counter_sample_desc)); counters.push_back(system_counter_t("sflow_raw_packet_headers_total", sflow_raw_packet_headers_total, metric_type_t::counter, sflow_raw_packet_headers_total_desc)); counters.push_back(system_counter_t("sflow_ipv4_header_protocol", sflow_ipv4_header_protocol, metric_type_t::counter, sflow_ipv4_header_protocol_desc)); counters.push_back(system_counter_t("sflow_ipv6_header_protocol", sflow_ipv6_header_protocol, metric_type_t::counter, sflow_ipv6_header_protocol_desc)); counters.push_back(system_counter_t("sflow_unknown_header_protocol", sflow_unknown_header_protocol, metric_type_t::counter, sflow_unknown_header_protocol_desc)); counters.push_back(system_counter_t("sflow_extended_router_data_records", sflow_extended_router_data_records, metric_type_t::counter, sflow_extended_router_data_records_desc)); counters.push_back(system_counter_t("sflow_extended_switch_data_records", sflow_extended_switch_data_records, metric_type_t::counter, sflow_extended_switch_data_records_desc)); counters.push_back(system_counter_t("sflow_extended_gateway_data_records", sflow_extended_gateway_data_records, metric_type_t::counter, sflow_extended_gateway_data_records_desc)); counters.push_back(system_counter_t("sflow_packets_discarded", sflow_packets_discarded, metric_type_t::counter, sflow_packets_discarded_desc)); return counters; } // Prototypes bool process_sflow_counter_sample(const uint8_t* data_pointer, size_t data_length, bool expanded, const sflow_packet_header_unified_accessor& sflow_header_accessor); process_packet_pointer sflow_process_func_ptr = NULL; void start_sflow_collector(const std::string& sflow_host, unsigned int sflow_port); // Initialize sflow module, we need it for allocation per module structures void init_sflow_module() { } // De-initialise sflow module, we need it for deallocation module structures void deinit_sflow_module() { } void start_sflow_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << plugin_log_prefix << "plugin started"; sflow_process_func_ptr = func_ptr; if (fastnetmon_global_configuration.sflow_ports.size() == 0) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "Please specify at least single port for sflow_port field"; return; } logger << log4cpp::Priority::DEBUG << plugin_log_prefix << "We parsed " << fastnetmon_global_configuration.sflow_ports.size() << " ports for sFlow"; boost::thread_group sflow_collector_threads; logger << log4cpp::Priority::DEBUG << plugin_log_prefix << "We will listen on " << fastnetmon_global_configuration.sflow_ports.size() << " ports"; for (auto sflow_port : fastnetmon_global_configuration.sflow_ports) { sflow_collector_threads.add_thread( new boost::thread(start_sflow_collector, fastnetmon_global_configuration.sflow_host, sflow_port)); } sflow_collector_threads.join_all(); } void start_sflow_collector(const std::string& sflow_host, unsigned int sflow_port) { logger << log4cpp::Priority::INFO << plugin_log_prefix << "plugin will listen on " << sflow_host << ":" << sflow_port << " udp port"; struct addrinfo hints; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; // AI_PASSIVE to handle empty sflow_host as bind on all interfaces // AI_NUMERICHOST to allow only numerical host hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; addrinfo* servinfo = NULL; std::string port_as_string = std::to_string(sflow_port); int getaddrinfo_result = getaddrinfo(sflow_host.c_str(), port_as_string.c_str(), &hints, &servinfo); if (getaddrinfo_result != 0) { logger << log4cpp::Priority::ERROR << "sFlow getaddrinfo function failed with code: " << getaddrinfo_result << " please check sflow_host syntax"; return; } int sockfd = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol); if (sockfd == -1) { logger << log4cpp::Priority::ERROR << "Cannot create socket with error " << errno << " error message: " << strerror(errno); return; } int bind_result = bind(sockfd, servinfo->ai_addr, servinfo->ai_addrlen); if (bind_result != 0) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "cannot bind on " << sflow_port << ":" << sflow_host << " with errno: " << errno << " error: " << strerror(errno); return; } freeaddrinfo(servinfo); /* 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)); int receive_buffer = 0; socklen_t value_length = sizeof(receive_buffer); // Get current read buffer size // 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 int get_buffer_size_res = getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (char*)&receive_buffer, &value_length); if (get_buffer_size_res != 0) { logger << log4cpp::Priority::ERROR << "Cannot retrieve default receive buffer size for sFlow"; } else { logger << log4cpp::Priority::INFO << "Default sFlow receive buffer size: " << receive_buffer << " bytes"; } while (true) { unsigned int udp_buffer_size = 65536; char udp_buffer[udp_buffer_size]; struct sockaddr_in client_addr; socklen_t address_len = sizeof(client_addr); int received_bytes = recvfrom(sockfd, udp_buffer, udp_buffer_size, 0, (struct sockaddr*)&client_addr, &address_len); if (received_bytes > 0) { raw_udp_packets_received++; uint32_t client_ipv4_address = 0; if (client_addr.sin_family == AF_INET) { client_ipv4_address = client_addr.sin_addr.s_addr; // Use most efficient function to avoid bottlenecks here std::string ip_address_as_string; // convert_ip_as_uint_to_string_fast(client_ipv4_address, ip_address_as_string); // logger << log4cpp::Priority::ERROR << "client ip: " << convert_ip_as_uint_to_string(client_ipv4_address); } else if (client_addr.sin_family == AF_INET6) { // We do not support them now } else { // Should not happen } parse_sflow_v5_packet((uint8_t*)udp_buffer, received_bytes, client_ipv4_address); } else { if (received_bytes == -1) { if (errno == EAGAIN) { // We got timeout, it's OK! udp_receive_eagain++; } else { udp_receive_errors++; logger << log4cpp::Priority::ERROR << plugin_log_prefix << "data receive failed"; } } } // Add interruption point for correct application shutdown boost::this_thread::interruption_point(); } } bool process_sflow_flow_sample(const uint8_t* data_pointer, size_t data_length, bool expanded, const sflow_packet_header_unified_accessor& sflow_header_accessor, uint32_t client_ipv4_address) { const uint8_t* current_packet_end = data_pointer + data_length; sflow_sample_header_unified_accessor_t sflow_sample_header_unified_accessor; bool read_sflow_sample_header_unified_result = read_sflow_sample_header_unified(sflow_sample_header_unified_accessor, data_pointer, data_length, expanded); if (!read_sflow_sample_header_unified_result) { sflow_bad_flow_samples++; logger << log4cpp::Priority::ERROR << plugin_log_prefix << "could not read sample header from the packet"; return false; } if (sflow_sample_header_unified_accessor.get_number_of_flow_records() == 0) { sflow_bad_flow_samples++; logger << log4cpp::Priority::ERROR << plugin_log_prefix << "for some strange reasons we got zero flow records"; return false; } if (sflow_sample_header_unified_accessor.get_number_of_flow_records() > max_number_of_flow_records) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "flow records number " << sflow_sample_header_unified_accessor.get_number_of_flow_records() << " exceeds maximum value " << max_number_of_flow_records; sflow_bad_flow_samples++; return false; } const uint8_t* flow_record_zone_start = data_pointer + sflow_sample_header_unified_accessor.get_original_payload_length(); std::vector vector_tuple; vector_tuple.reserve(sflow_sample_header_unified_accessor.get_number_of_flow_records()); bool padding_found = false; bool get_records_result = get_records(vector_tuple, flow_record_zone_start, sflow_sample_header_unified_accessor.get_number_of_flow_records(), current_packet_end, padding_found); // I think that it's pretty important to have counter for this case if (padding_found) { sflow_padding_flow_sample++; } if (!get_records_result) { sflow_bad_flow_samples++; logger << log4cpp::Priority::ERROR << plugin_log_prefix << "Could not get records for some reasons"; return false; } simple_packet_t packet; packet.source = SFLOW; packet.arrival_time = current_inaccurate_time; packet.agent_ip_address = client_ipv4_address; for (auto record : vector_tuple) { int32_t record_type = std::get<0>(record); const uint8_t* payload_ptr = std::get<1>(record); int32_t record_length = std::get<2>(record); // std::cout << "flow record " << " record_type: " << record_type // << " record_length: " << record_length << std::endl; // raw packet header we support only it if (record_type == SFLOW_RECORD_TYPE_RAW_PACKET_HEADER) { sflow_raw_packet_headers_total++; sflow_raw_protocol_header_t sflow_raw_protocol_header; memcpy(&sflow_raw_protocol_header, payload_ptr, sizeof(sflow_raw_protocol_header_t)); sflow_raw_protocol_header.network_to_host_byte_order(); // logger << log4cpp::Priority::DEBUG << "Raw protocol header: " << sflow_raw_protocol_header.print(); const uint8_t* header_payload_pointer = payload_ptr + sizeof(sflow_raw_protocol_header_t); if (sflow_raw_protocol_header.header_protocol == SFLOW_HEADER_PROTOCOL_ETHERNET) { parser_options_t parser_options{}; parser_options.unpack_gre = fastnetmon_global_configuration.sflow_extract_tunnel_traffic; parser_options.read_packet_length_from_ip_header = fastnetmon_global_configuration.sflow_read_packet_length_from_ip_header; auto result = parse_raw_packet_to_simple_packet_full_ng(header_payload_pointer, sflow_raw_protocol_header.frame_length_before_sampling, sflow_raw_protocol_header.header_size, packet, parser_options); if (result != network_data_stuctures::parser_code_t::success) { sflow_parse_error_nested_header++; logger << log4cpp::Priority::DEBUG << plugin_log_prefix << "Cannot parse nested packet using ng parser: " << parser_code_to_string(result); return false; } } else if (sflow_raw_protocol_header.header_protocol == SFLOW_HEADER_PROTOCOL_IPv4) { // It's IPv4 without Ethernet header at all sflow_ipv4_header_protocol++; parser_options_t parser_options_ipv4{}; parser_options_ipv4.unpack_gre = false; parser_options_ipv4.read_packet_length_from_ip_header = fastnetmon_global_configuration.sflow_read_packet_length_from_ip_header; // We parse this packet using special version of our parser which looks only on IPv4 packet auto result = parse_raw_ipv4_packet_to_simple_packet_full_ng(header_payload_pointer, sflow_raw_protocol_header.frame_length_before_sampling, sflow_raw_protocol_header.header_size, packet, parser_options_ipv4); if (result != network_data_stuctures::parser_code_t::success) { sflow_parse_error_nested_header++; logger << log4cpp::Priority::DEBUG << plugin_log_prefix << "Cannot parse nested IPv4 packet using ng parser: " << parser_code_to_string(result); return false; } } else if (sflow_raw_protocol_header.header_protocol == SFLOW_HEADER_PROTOCOL_IPv6) { // It's IPv6 without Ethernet header at all sflow_ipv6_header_protocol++; return false; } else { // Something really unusual, MPLS? sflow_unknown_header_protocol++; return false; } // That's actually WEIRD as we touch these fields in packet parser logic too. // So basically we do not use logic from parsers above and override values after parser finishes work // Pass pointer to raw header to FastNetMon processing functions packet.payload_pointer = header_payload_pointer; packet.payload_full_length = sflow_raw_protocol_header.frame_length_before_sampling; packet.captured_payload_length = sflow_raw_protocol_header.header_size; packet.sample_ratio = sflow_sample_header_unified_accessor.sampling_rate; packet.input_interface = sflow_sample_header_unified_accessor.input_port_index; packet.output_interface = sflow_sample_header_unified_accessor.output_port_index; // In sFlow we have special way to learn that packet was discarded from type of output interface if (sflow_sample_header_unified_accessor.output_port_type == SFLOW_PORT_FORMAT_PACKET_DISCARDED) { packet.forwarding_status = forwarding_status_t::dropped; sflow_packets_discarded++; } // std::cout << print_simple_packet(packet) << std::endl; } else if (record_type == SFLOW_RECORD_TYPE_EXTENDED_ROUTER_DATA) { sflow_extended_router_data_records++; } else if (record_type == SFLOW_RECORD_TYPE_EXTENDED_SWITCH_DATA) { sflow_extended_switch_data_records++; } else if (record_type == SFLOW_RECORD_TYPE_EXTENDED_GATEWAY_DATA) { sflow_extended_gateway_data_records++; if (record_length < sizeof(uint32_t)) { logger << log4cpp::Priority::ERROR << "Extended gateway data is too short: " << record_length; return false; } // First field here is address type for nexthop uint32_t nexthop_address_type = 0; memcpy(&nexthop_address_type, payload_ptr, sizeof(uint32_t)); if (fast_ntoh(nexthop_address_type) == SFLOW_ADDRESS_TYPE_IPv4) { // We can parse first more important for us fields from gateway structure if (record_length < sizeof(sflow_extended_gateway_information_t)) { logger << log4cpp::Priority::ERROR << "Extended gateway data is too short for IPv structure: " << record_length; return false; } // We're ready to parse it const sflow_extended_gateway_information_t* gateway_details = (const sflow_extended_gateway_information_t*)payload_ptr; packet.src_asn = fast_ntoh(gateway_details->router_asn); packet.dst_asn = fast_ntoh(gateway_details->source_asn); } // logger << log4cpp::Priority::DEBUG << "Address type: " << fast_ntoh(*address_type); } else { // unknown type } } sflow_process_func_ptr(packet); return true; } // Read sFLOW packet header // Awesome description about v5 format from AMX-IX folks: // Header structure from AMS-IX folks: // http://www.sflow.org/developers/diagrams/sFlowV5Datagram.pdf void parse_sflow_v5_packet(const uint8_t* payload_ptr, unsigned int payload_length, uint32_t client_ipv4_address) { sflow_packet_header_unified_accessor sflow_header_accessor; const uint8_t* total_packet_end = payload_ptr + payload_length; // Increase total number of packets sflow_total_packets++; bool read_sflow_header_result = read_sflow_header(payload_ptr, payload_length, sflow_header_accessor); if (!read_sflow_header_result) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "could not read sFlow packet header correctly"; sflow_bad_packets++; return; } if (sflow_header_accessor.get_datagram_samples_count() <= 0) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "Strange number of sFlow samples: " << sflow_header_accessor.get_datagram_samples_count(); sflow_bad_packets++; return; } // As we're going to allocate memory using this value as number of elements // we need to ensure that we capped it by reasonable value if (sflow_header_accessor.get_datagram_samples_count() > max_sflow_sample_number) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "Number of sFlow samples in packet " << sflow_header_accessor.get_datagram_samples_count() << " exceeds allowed maximum value " << max_sflow_sample_number; sflow_bad_packets++; return; } std::vector samples_vector; samples_vector.reserve(sflow_header_accessor.get_datagram_samples_count()); const uint8_t* samples_block_start = payload_ptr + sflow_header_accessor.get_original_payload_length(); bool discovered_padding = false; bool get_all_samples_result = get_all_samples(samples_vector, samples_block_start, total_packet_end, sflow_header_accessor.get_datagram_samples_count(), discovered_padding); if (!get_all_samples_result) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we could not extract all samples from packet"; sflow_bad_packets++; return; } if (discovered_padding) { sflow_with_padding_at_the_end_of_packet++; } for (auto sample : samples_vector) { // enterprise, sample_format, data start address, data region length // std::cout << "We process #" << i << " sample with format " << format // << " enterprise " << enterprise // << " and length " << sample_length << std::endl; int32_t enterprise = std::get<0>(sample); int32_t integer_format = std::get<1>(sample); const uint8_t* data_pointer = std::get<2>(sample); size_t data_length = std::get<3>(sample); if (enterprise != 0) { // We do not support vendor specific additions continue; } sflow_sample_type_t sample_format = sflow_sample_type_from_integer(integer_format); if (sample_format == sflow_sample_type_t::BROKEN_TYPE) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we got broken format type number: " << integer_format; continue; } if (sample_format == sflow_sample_type_t::FLOW_SAMPLE) { // logger << log4cpp::Priority::DEBUG << plugin_log_prefix << "We got flow sample"; process_sflow_flow_sample(data_pointer, data_length, false, sflow_header_accessor, client_ipv4_address); sflow_flow_samples++; } else if (sample_format == sflow_sample_type_t::COUNTER_SAMPLE) { // logger << log4cpp::Priority::DEBUG << plugin_log_prefix << "We got counter sample"; process_sflow_counter_sample(data_pointer, data_length, false, sflow_header_accessor); sflow_counter_sample++; } else if (sample_format == sflow_sample_type_t::EXPANDED_FLOW_SAMPLE) { // logger << log4cpp::Priority::DEBUG << plugin_log_prefix << "We got expanded flow sample"; process_sflow_flow_sample(data_pointer, data_length, true, sflow_header_accessor, client_ipv4_address); sflow_flow_samples++; } else if (sample_format == sflow_sample_type_t::EXPANDED_COUNTER_SAMPLE) { // logger << log4cpp::Priority::DEBUG << plugin_log_prefix << "We got expanded counter sample"; // process_sflow_counter_sample(data_pointer, data_length, true, sflow_header_accessor); sflow_expanded_counter_sample++; } else { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we got broken format type: " << integer_format; } } } bool process_sflow_counter_sample(const uint8_t* data_pointer, size_t data_length, bool expanded, const sflow_packet_header_unified_accessor& sflow_header_accessor) { sflow_counter_header_unified_accessor_t sflow_counter_header_unified_accessor; bool read_sflow_counter_header_result = read_sflow_counter_header(data_pointer, data_length, expanded, sflow_counter_header_unified_accessor); if (!read_sflow_counter_header_result) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "could not read sFlow counter header"; return false; } if (sflow_counter_header_unified_accessor.get_number_of_counter_records() == 0) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "get zero number of counter records"; return false; } if (sflow_counter_header_unified_accessor.get_number_of_counter_records() > max_number_of_counter_records) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "number of counter records " << sflow_counter_header_unified_accessor.get_number_of_counter_records() << " exceeds maximum value " << max_number_of_counter_records; return false; } std::vector counter_record_sample_vector; counter_record_sample_vector.reserve(sflow_counter_header_unified_accessor.get_number_of_counter_records()); bool get_all_counter_records_result = get_all_counter_records(counter_record_sample_vector, data_pointer + sflow_counter_header_unified_accessor.get_original_payload_length(), data_pointer + data_length, sflow_counter_header_unified_accessor.get_number_of_counter_records()); if (!get_all_counter_records_result) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "could not get all counter records"; return false; } for (const auto& counter_record : counter_record_sample_vector) { if (counter_record.enterprise != 0) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we do not support vendor specific enterprise numbers"; continue; } sample_counter_types_t sample_type = sample_counter_types_t::BROKEN_COUNTER; if (counter_record.format == 1) { sample_type = sample_counter_types_t::GENERIC_INTERFACE_COUNTERS; } else if (counter_record.format == 2) { sample_type = sample_counter_types_t::ETHERNET_INTERFACE_COUNTERS; } if (sample_type == sample_counter_types_t::GENERIC_INTERFACE_COUNTERS) { // logger << log4cpp::Priority::DEBUG << plugin_log_prefix << "GENERIC_INTERFACE_COUNTERS"; if (sizeof(generic_sflow_interface_counters_t) != counter_record.length) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "length mismatch for generic interface counter packet: " << counter_record.length; continue; } sflow_generic_interface_counter_sample++; const generic_sflow_interface_counters_t* generic_sflow_interface_counters = (const generic_sflow_interface_counters_t*)(counter_record.pointer); if (logger.getPriority() == log4cpp::Priority::DEBUG) { logger << log4cpp::Priority::DEBUG << generic_sflow_interface_counters->print(); } // TODO: we need to get relevant fields in special intermediate structure and then pass them for processing // somwhere else I think we need one dedicated thread to implement such metrics pusshes to Clickhouse } else if (sample_type == sample_counter_types_t::ETHERNET_INTERFACE_COUNTERS) { // These counters provide too detailed information about interface and we do not need it on such level for DDoS detection purposes } } return true; } upstream-fastnetmon/src/sflow_plugin/sflow_collector.hpp0000664000175000017500000000056715060514305022077 0ustar meme#pragma once #include "../fastnetmon_types.hpp" #include void start_sflow_collection(process_packet_pointer func_ptr); void init_sflow_module(); void deinit_sflow_module(); // New code for v5 only void parse_sflow_v5_packet(const uint8_t* payload_ptr, unsigned int payload_length, uint32_t client_ipv4_address); std::vector get_sflow_stats(); upstream-fastnetmon/src/vcpkg.json0000664000175000017500000000035715060514305015460 0ustar meme{ "name": "fastnetmon", "version-string": "1.2.4", "dependencies": [ "openssl", "log4cpp", "capnproto", "boost", "grpc", "protobuf", "librdkafka", "cppkafka", "mongo-c-driver", "hiredis" ] } upstream-fastnetmon/src/iana_ethertypes.hpp0000664000175000017500000000102715060514305017343 0ustar meme#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, }; upstream-fastnetmon/src/bgp_protocol.cpp0000664000175000017500000005433315060514305016653 0ustar meme#include "bgp_protocol.hpp" #include #include "fast_library.hpp" #include "network_data_structures.hpp" #ifdef _WIN32 #include #else #include #include #include #endif #include #include #include #include #include "nlohmann/json.hpp" uint32_t convert_cidr_to_binary_netmask_local_function_copy(unsigned int cidr) { uint32_t binary_netmask = 0xFFFFFFFF; binary_netmask = binary_netmask << (32 - cidr); // htonl from host byte order to network // ntohl from network byte order to host // We need network byte order at output return htonl(binary_netmask); } bool decode_nlri_ipv4(int len, uint8_t* value, subnet_cidr_mask_t& extracted_prefix) { uint32_t not_used_number_of_scanned_bytes = 0; return decode_bgp_subnet_encoding_ipv4(len, value, extracted_prefix, not_used_number_of_scanned_bytes); } // https://www.ietf.org/rfc/rfc4271.txt /* a) Length: The Length field indicates the length in bits of the IP address prefix. A length of zero indicates a prefix that matches all IP addresses (with prefix, itself, of zero octets). b) Prefix: The Prefix field contains an IP address prefix, followed by the minimum number of trailing bits needed to make the end of the field fall on an octet boundary. Note that the value of trailing bits is irrelevant. */ // https://github.com/Exa-Networks/exabgp/blob/master/lib/exabgp/bgp/message/update/nlri/cidr.py#L81 // https://github.com/osrg/gobgp/blob/d6148c75a30d87c3f8c1d0f68725127e4c5f3a65/packet/bgp.go#L700 bool decode_bgp_subnet_encoding_ipv4(int len, uint8_t* value, subnet_cidr_mask_t& extracted_prefix, uint32_t& parsed_nlri_length) { // We have NLRI only for IPv4 announces! IPv6 and Flow Spec do not use it! if (len == 0 or value == NULL) { logger << log4cpp::Priority::WARN << "NLRI content is blank for this announce"; return false; } uint8_t prefix_bit_length = value[0]; // logger << log4cpp::Priority::WARN << "We extracted prefix length: " << // int(prefix_bit_length) << // std::endl; // Rounds x upward uint32_t prefix_byte_length = how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_bit_length); // 1 means 1 byte size for prefix_bit_length itself uint32_t full_nlri_length = prefix_byte_length + 1; if (len < full_nlri_length) { logger << log4cpp::Priority::WARN << "Not enough data size! We need least " << prefix_byte_length << " bytes of data"; return false; } // We need number of scanned bytes for next parses parsed_nlri_length = full_nlri_length; return decode_bgp_subnet_encoding_ipv4_raw(value, extracted_prefix); } // Same as decode_bgp_subnet_encoding but do not check array bounds at all // We need to ensure about enough length of data array before calling of this // code! bool decode_bgp_subnet_encoding_ipv4_raw(uint8_t* value, subnet_cidr_mask_t& extracted_prefix) { if (value == NULL) { logger << log4cpp::Priority::WARN << "Zero value unexpected for decode_bgp_subnet_encoding_ipv4_raw"; return false; } uint8_t prefix_bit_length = value[0]; uint32_t prefix_byte_length = how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_bit_length); // 1 means 1 byte size for prefix_bit_length itself // uint32_t full_nlri_length = prefix_byte_length + 1; uint32_t prefix_ipv4 = 0; memcpy(&prefix_ipv4, value + 1, prefix_byte_length); // Then we should set to zero all non important bits in address because they // could store weird // information uint32_t subnet_address_netmask_binary = convert_cidr_to_binary_netmask_local_function_copy(prefix_bit_length); // Remove useless bits with this approach prefix_ipv4 = prefix_ipv4 & subnet_address_netmask_binary; // logger << log4cpp::Priority::WARN << "Extracted prefix: " << // convert_ip_as_uint_to_string(prefix_ipv4) << "/" << // int(prefix_bit_length) ; extracted_prefix.subnet_address = prefix_ipv4; extracted_prefix.cidr_prefix_length = prefix_bit_length; return true; } bool decode_ipv6_announce_from_binary_encoded_atributes(std::vector binary_attributes, IPv6UnicastAnnounce& ipv6_announce) { for (auto binary_attribute : binary_attributes) { bgp_attibute_common_header_t bgp_attibute_common_header; bool bgp_attrinute_read_result = bgp_attibute_common_header.parse_raw_bgp_attribute_binary_buffer(binary_attribute); if (!bgp_attrinute_read_result) { logger << log4cpp::Priority::WARN << "Could not read BGP attribute to common structure"; return false; } // bgp_attribute_common_header.print() ; if (bgp_attibute_common_header.attribute_type == BGP_ATTRIBUTE_MP_REACH_NLRI) { bool ipv6_decode_result = decode_mp_reach_ipv6(binary_attribute.get_used_size(), (uint8_t*)binary_attribute.get_pointer(), bgp_attibute_common_header, ipv6_announce); if (!ipv6_decode_result) { logger << log4cpp::Priority::ERROR << "Can't decode IPv6 announce"; return false; } } } return true; } // Decodes MP Reach NLRI attribute and populates IPv6 specific fields bool decode_mp_reach_ipv6(int len, uint8_t* value, bgp_attibute_common_header_t bgp_attibute_common_header, IPv6UnicastAnnounce& ipv6_announce) { // TODO: we should add sanity checks to avoid reads after attribute's memory block uint8_t* mp_reach_attribute_shift = (uint8_t*)value + bgp_attibute_common_header.attribute_body_shift; // Read first part of MP Reach NLRI header bgp_mp_reach_short_header_t* bgp_mp_ext_header = (bgp_mp_reach_short_header_t*)mp_reach_attribute_shift; bgp_mp_ext_header->network_to_host_byte_order(); // logger << log4cpp::Priority::INFO << bgp_mp_ext_header->print(); if (not(bgp_mp_ext_header->afi_identifier == AFI_IP6 and bgp_mp_ext_header->safi_identifier == SAFI_UNICAST)) { logger << log4cpp::Priority::WARN << "We have got unexpected afi or safi numbers from IPv6 MP Reach NLRI"; return false; } subnet_ipv6_cidr_mask_t next_hop_ipv6; // We support only 16 byte (/128) next hops next_hop_ipv6.cidr_prefix_length = 128; //-V1048 if (bgp_mp_ext_header->length_of_next_hop != 16) { logger << log4cpp::Priority::WARN << "We support only 16 byte next hop for IPv6 MP Reach NLRI"; return false; } memcpy(&next_hop_ipv6.subnet_address, mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t), bgp_mp_ext_header->length_of_next_hop); ipv6_announce.set_next_hop(next_hop_ipv6); // logger << log4cpp::Priority::INFO << "IPv6 next hop is: "<< convert_ipv6_subnet_to_string(next_hop_ipv6); // Strip single byte reserved field uint8_t* prefix_length = mp_reach_attribute_shift + sizeof(bgp_mp_reach_short_header_t) + bgp_mp_ext_header->length_of_next_hop + sizeof(uint8_t); // We should cast it to int for proper print // logger << log4cpp::Priority::INFO << "NLRI length: " << int(*prefix_length); uint32_t number_of_bytes_required_for_prefix = how_much_bytes_we_need_for_storing_certain_subnet_mask(*prefix_length); // logger << log4cpp::Priority::INFO << "We need " << number_of_bytes_required_for_prefix << " bytes for this prefix"; subnet_ipv6_cidr_mask_t prefix_ipv6; prefix_ipv6.cidr_prefix_length = *prefix_length; // Strip single byte for prefix_length and read network address memcpy(&prefix_ipv6.subnet_address, prefix_length + sizeof(uint8_t), number_of_bytes_required_for_prefix); ipv6_announce.set_prefix(prefix_ipv6); // logger << log4cpp::Priority::INFO << "Prefix is: " << convert_ipv6_subnet_to_string(prefix_ipv6); return true; } // https://github.com/osrg/gobgp/blob/d6148c75a30d87c3f8c1d0f68725127e4c5f3a65/packet/bgp.go#L5940 bool decode_attribute(int len, char* value, IPv4UnicastAnnounce& unicast_ipv4_announce) { bgp_attibute_common_header_t bgp_attibute_common_header; bool bgp_attrinute_read_result = bgp_attibute_common_header.parse_raw_bgp_attribute((uint8_t*)value, len); if (!bgp_attrinute_read_result) { logger << log4cpp::Priority::WARN << "Could not read BGP attribute to common structure"; return false; } switch (bgp_attibute_common_header.attribute_type) { case BGP_ATTRIBUTE_ORIGIN: { if (bgp_attibute_common_header.attribute_value_length != 1) { logger << log4cpp::Priority::WARN << "Broken size for BGP_ATTRIBUTE_ORIGIN: " << bgp_attibute_common_header.attribute_value_length; return false; } uint8_t origin_value = 0; memcpy(&origin_value, value + bgp_attibute_common_header.attribute_flag_and_type_length + bgp_attibute_common_header.length_of_length_field, sizeof(origin_value)); // logger << log4cpp::Priority::WARN << "BGP_ATTRIBUTE_ORIGIN: " << // get_origin_name_by_value(origin_value) << // std::endl; unicast_ipv4_announce.set_origin((BGP_ORIGIN_TYPES)origin_value); } break; case BGP_ATTRIBUTE_AS_PATH: { logger << log4cpp::Priority::DEBUG << "Got BGP_ATTRIBUTE_AS_PATH but I do not have code for parsing it"; } break; case BGP_ATTRIBUTE_NEXT_HOP: { if (bgp_attibute_common_header.attribute_value_length == 4) { uint32_t nexthop_value = 0; memcpy(&nexthop_value, value + bgp_attibute_common_header.attribute_flag_and_type_length + bgp_attibute_common_header.length_of_length_field, sizeof(nexthop_value)); // logger << log4cpp::Priority::WARN << "BGP_ATTRIBUTE_NEXT_HOP value is: " << convert_ip_as_uint_to_string(nexthop_value); unicast_ipv4_announce.set_next_hop(nexthop_value); } else if (bgp_attibute_common_header.attribute_value_length == 16) { logger << log4cpp::Priority::WARN << "BGP_ATTRIBUTE_NEXT_HOP is not supported yet for IPv6"; } else { logger << log4cpp::Priority::ERROR << "Wrong next hop length: " << bgp_attibute_common_header.attribute_value_length; return false; } } break; case BGP_ATTRIBUTE_MULTI_EXIT_DISC: { logger << log4cpp::Priority::DEBUG << "Got BGP_ATTRIBUTE_MULTI_EXIT_DISC but I do not have code for parsing it"; } break; case BGP_ATTRIBUTE_LOCAL_PREF: { logger << log4cpp::Priority::DEBUG << "Got BGP_ATTRIBUTE_LOCAL_PREF but I do not have code for parsing it"; } break; case BGP_ATTRIBUTE_COMMUNITY: { logger << log4cpp::Priority::DEBUG << "Got BGP_ATTRIBUTE_COMMUNITY but I do not have code for parsing it"; } break; case BGP_ATTRIBUTE_MP_REACH_NLRI: { logger << log4cpp::Priority::WARN << "BGP_ATTRIBUTE_MP_REACH_NLRI with length " << bgp_attibute_common_header.attribute_value_length; // TODO: I call this code only for testing purposes // IPv6UnicastAnnounce ipv6_announce; // decode_mp_reach_ipv6(len, value, bgp_attibute_common_header, ipv6_announce); // logger << log4cpp::Priority::WARN << ipv6_announce.print(); } break; case BGP_ATTRIBUTE_EXTENDED_COMMUNITY: { logger << log4cpp::Priority::DEBUG << "BGP_ATTRIBUTE_EXTENDED_COMMUNITY with length: " << bgp_attibute_common_header.attribute_value_length; } break; default: { logger << log4cpp::Priority::DEBUG << "Unknown attribute: " << int(bgp_attibute_common_header.attribute_type); } break; } return true; } // Prepare MP Reach IPv6 attribute for IPv6 traffic // This function creates only internal payload without attribute headers bool encode_ipv6_announces_into_bgp_mp_reach_attribute_internal(const IPv6UnicastAnnounce& ipv6_announce, dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute) { // Create internal content of IPv6 MP Reach NLRI bgp_mp_reach_ipv6_attribute.set_maximum_buffer_size_in_bytes(2048); /* +---------------------------------------------------------+ | Address Family Identifier (2 octets) | +---------------------------------------------------------+ | Subsequent Address Family Identifier (1 octet) | +---------------------------------------------------------+ | Length of Next Hop Network Address (1 octet) | +---------------------------------------------------------+ | Network Address of Next Hop (variable) | +---------------------------------------------------------+ | Reserved (1 octet) | +---------------------------------------------------------+ | Network Layer Reachability Information (variable) | +---------------------------------------------------------+ */ // Create short header bgp_mp_reach_short_header_t bgp_mp_reach_short_header; bgp_mp_reach_short_header.afi_identifier = AFI_IP6; //-V1048 bgp_mp_reach_short_header.safi_identifier = SAFI_UNICAST; //-V1048 // Add next hop field bgp_mp_reach_short_header.length_of_next_hop = 16; //-V1048 bgp_mp_reach_short_header.host_byte_order_to_network_byte_order(); // Add next hop field bgp_mp_reach_ipv6_attribute.append_data_as_object_ptr(&bgp_mp_reach_short_header); auto next_hop = ipv6_announce.get_next_hop(); logger << log4cpp::Priority::DEBUG << "Append next hop " << convert_ipv6_subnet_to_string(next_hop) << " with length of " << sizeof(next_hop.subnet_address) << " bytes"; bgp_mp_reach_ipv6_attribute.append_data_as_pointer(&next_hop.subnet_address, sizeof(next_hop.subnet_address)); // Append reserved byte uint8_t reserved_byte = 0; bgp_mp_reach_ipv6_attribute.append_byte(reserved_byte); // Get prefix for announce auto prefix = ipv6_announce.get_prefix(); logger << log4cpp::Priority::DEBUG << "Extracted prefix " << convert_ipv6_subnet_to_string(prefix); if (!encode_ipv6_prefix(prefix, bgp_mp_reach_ipv6_attribute)) { logger << log4cpp::Priority::ERROR << "Cannot encode IPv6 prefix"; return false; } return true; } // Encodes IPv6 in BGP encoding format: // Prefix length followed by prefix itself // NB! You have to initialise dynamic_buffer before calling it bool encode_ipv6_prefix(const subnet_ipv6_cidr_mask_t& prefix, dynamic_binary_buffer_t& dynamic_buffer) { // Add prefix length uint8_t prefix_length = uint8_t(prefix.cidr_prefix_length); logger << log4cpp::Priority::DEBUG << "Prefix length: " << uint32_t(prefix_length); if (!dynamic_buffer.append_byte(prefix_length)) { logger << log4cpp::Priority::ERROR << "Cannot add prefix length"; return false; } uint32_t prefix_byte_length = how_much_bytes_we_need_for_storing_certain_subnet_mask(prefix_length); logger << log4cpp::Priority::DEBUG << "We need " << int(prefix_byte_length) << " bytes to encode IPv6 prefix " << convert_ipv6_subnet_to_string(prefix); // We should copy only first meaningful bytes if (!dynamic_buffer.append_data_as_pointer(&prefix.subnet_address, prefix_byte_length)) { logger << log4cpp::Priority::ERROR << "Cannot add prefix itself"; return false; } return true; } bool encode_ipv6_announces_into_bgp_mp_reach_attribute(const IPv6UnicastAnnounce& ipv6_announce, dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute) { dynamic_binary_buffer_t mp_nlri_binary_buffer; bool mp_nlri_encode_result = encode_ipv6_announces_into_bgp_mp_reach_attribute_internal(ipv6_announce, mp_nlri_binary_buffer); if (!mp_nlri_encode_result) { logger << log4cpp::Priority::ERROR << "Can't create inner MP Reach attribute for IPv6"; return false; } // uint8_t nlri_length = mp_nlri_binary_buffer.get_used_size(); // logger << log4cpp::Priority::INFO << "Crafter mp reach with size: " << int(nlri_length); // Create attribute header bgp_attribute_multiprotocol_extensions_t bgp_attribute_multiprotocol_extensions; bgp_attribute_multiprotocol_extensions.attribute_length = mp_nlri_binary_buffer.get_used_size(); bgp_mp_reach_ipv6_attribute.set_maximum_buffer_size_in_bytes(2048); bgp_mp_reach_ipv6_attribute.append_data_as_object_ptr(&bgp_attribute_multiprotocol_extensions); bgp_mp_reach_ipv6_attribute.append_dynamic_buffer(mp_nlri_binary_buffer); if (bgp_mp_reach_ipv6_attribute.is_failed()) { logger << log4cpp::Priority::WARN << "We have issues with binary buffer in IPv6 NLRI generation code"; return false; } return true; } // TODO: add sanity checks // If you want to improve this code with eliminating memory copy // Please read this https://en.wikipedia.org/wiki/Return_value_optimization bool encode_bgp_subnet_encoding(const subnet_cidr_mask_t& prefix, dynamic_binary_buffer_t& dynamic_binary_buffer) { uint32_t subnet_address = prefix.subnet_address; uint32_t prefix_bit_length = prefix.cidr_prefix_length; // Rounds x upward uint32_t prefix_byte_length = ceil(float(prefix_bit_length) / 8); // We need 1 byte for prefix length in bits and X bytes for prefix itself uint32_t full_nlri_length = 1 + prefix_byte_length; // logger << log4cpp::Priority::WARN << "We will allocate " << // full_nlri_length << " bytes in buffer" // ; bool allocation_result = dynamic_binary_buffer.set_maximum_buffer_size_in_bytes(full_nlri_length); if (!allocation_result) { logger << log4cpp::Priority::WARN << "Allocation error"; return false; } dynamic_binary_buffer.append_byte(uint8_t(prefix_bit_length)); // Then we should set to zero all non important bits in address because they // could store weird // information uint32_t subnet_address_netmask_binary = convert_cidr_to_binary_netmask_local_function_copy(prefix_bit_length); // Zeroify useless bits subnet_address = subnet_address & subnet_address_netmask_binary; dynamic_binary_buffer.append_data_as_pointer(&subnet_address, prefix_byte_length); return true; } std::string get_origin_name_by_value(uint8_t origin_value) { switch (origin_value) { case BGP_ORIGIN_IGP: return "BGP_ORIGIN_IGP"; break; case BGP_ORIGIN_EGP: return "BGP_ORIGIN_EGP"; break; case BGP_ORIGIN_INCOMPLETE: return "BGP_ORIGIN_INCOMPLETE"; break; default: return "Unknown"; break; } } std::string get_bgp_attribute_name_by_number(uint8_t bgp_attribute_type) { switch (bgp_attribute_type) { case BGP_ATTRIBUTE_ORIGIN: return "BGP_ATTRIBUTE_ORIGIN"; break; case BGP_ATTRIBUTE_AS_PATH: return "BGP_ATTRIBUTE_AS_PATH"; break; case BGP_ATTRIBUTE_NEXT_HOP: return "BGP_ATTRIBUTE_NEXT_HOP"; break; case BGP_ATTRIBUTE_MULTI_EXIT_DISC: return "BGP_ATTRIBUTE_MULTI_EXIT_DISC"; break; case BGP_ATTRIBUTE_LOCAL_PREF: return "BGP_ATTRIBUTE_LOCAL_PREF"; break; case BGP_ATTRIBUTE_ATOMIC_AGGREGATE: return "BGP_ATTRIBUTE_ATOMIC_AGGREGATE"; break; case BGP_ATTRIBUTE_AGGREGATOR: return "BGP_ATTRIBUTE_AGGREGATOR"; break; case BGP_ATTRIBUTE_COMMUNITY: return "BGP_ATTRIBUTE_COMMUNITY"; break; case BGP_ATTRIBUTE_MP_REACH_NLRI: return "BGP_ATTRIBUTE_MP_REACH_NLRI"; break; case BGP_ATTRIBUTE_EXTENDED_COMMUNITY: return "BGP_ATTRIBUTE_EXTENDED_COMMUNITY"; break; default: return "UNKNOWN"; break; } } // This function calculates number of bytes required for store some certain // network // This is very useful if you are working with BGP encoded subnets uint32_t how_much_bytes_we_need_for_storing_certain_subnet_mask(uint8_t prefix_bit_length) { return ceil(float(prefix_bit_length) / 8); } // Wrapper function which just checks correctness of bgp community bool is_bgp_community_valid(std::string community_as_string) { bgp_community_attribute_element_t bgp_community_attribute_element; return read_bgp_community_from_string(community_as_string, bgp_community_attribute_element); } bool read_bgp_community_from_string(std::string community_as_string, bgp_community_attribute_element_t& bgp_community_attribute_element) { std::vector community_as_vector; split(community_as_vector, community_as_string, boost::is_any_of(":"), boost::token_compress_on); if (community_as_vector.size() != 2) { logger << log4cpp::Priority::WARN << "Could not parse community: " << community_as_string; return false; } int asn_as_integer = 0; if (!convert_string_to_positive_integer_safe(community_as_vector[0], asn_as_integer)) { logger << log4cpp::Priority::WARN << "Could not parse ASN from raw format: " << community_as_vector[0]; return false; } int community_number_as_integer = 0; if (!convert_string_to_positive_integer_safe(community_as_vector[1], community_number_as_integer)) { logger << log4cpp::Priority::WARN << "Could not parse community from raw format: " << community_as_vector[0]; return false; } if (asn_as_integer < 0 or community_number_as_integer < 0) { logger << log4cpp::Priority::WARN << "For some strange reasons we've got negative ASN or community numbers"; return false; } if (asn_as_integer > UINT16_MAX) { logger << log4cpp::Priority::ERROR << "Your ASN value exceeds maximum allowed value " << UINT16_MAX; return false; } if (community_number_as_integer > UINT16_MAX) { logger << log4cpp::Priority::ERROR << "Your community value exceeds maximum allowed value " << UINT16_MAX; return false; } bgp_community_attribute_element.asn_number = asn_as_integer; bgp_community_attribute_element.community_number = community_number_as_integer; return true; } upstream-fastnetmon/src/bgp_protocol_flow_spec.hpp0000664000175000017500000006741115060514305020722 0ustar meme#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_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); upstream-fastnetmon/src/fast_endianless.hpp0000664000175000017500000000344215060514305017324 0ustar meme#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; upstream-fastnetmon/src/metrics/0000755000175000017500000000000015060514305015112 5ustar memeupstream-fastnetmon/src/metrics/influxdb.cpp0000664000175000017500000006502715060514305017445 0ustar meme#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(); } upstream-fastnetmon/src/metrics/influxdb.hpp0000664000175000017500000000032315060514305017436 0ustar meme#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(); upstream-fastnetmon/src/metrics/graphite.hpp0000644000175000017500000000027514232165256017441 0ustar meme#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(); upstream-fastnetmon/src/metrics/graphite.cpp0000664000175000017500000002211315060514305017422 0ustar meme#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)); } } upstream-fastnetmon/src/metrics/clickhouse.cpp0000664000175000017500000012660615060514305017764 0ustar meme#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; } } upstream-fastnetmon/src/metrics/clickhouse.hpp0000664000175000017500000000075515060514305017765 0ustar meme#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); upstream-fastnetmon/src/fastnetmon_tests.cpp0000664000175000017500000000767615060514305017572 0ustar meme#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"); } upstream-fastnetmon/src/speed_counters.hpp0000664000175000017500000000200015060514305017171 0ustar meme#pragma once #include "fastnetmon_types.hpp" void increment_incoming_counters(subnet_counter_t& current_element, const simple_packet_t& current_packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes); void build_speed_counters_from_packet_counters(subnet_counter_t& new_speed_element, const subnet_counter_t& data_counter, double speed_calc_period); void increment_outgoing_counters(subnet_counter_t& current_element, const simple_packet_t& current_packet, uint64_t sampled_number_of_packets, uint64_t sampled_number_of_bytes); void build_average_speed_counters_from_speed_counters(subnet_counter_t& current_average_speed_element, const subnet_counter_t& new_speed_element, double exp_value); upstream-fastnetmon/src/xdp_plugin/0000775000175000017500000000000015060514305015617 5ustar memeupstream-fastnetmon/src/xdp_plugin/xdp_collector.cpp0000664000175000017500000005474615060514305021204 0ustar meme#include "xdp_collector.hpp" #include "../fastnetmon_plugin.hpp" #include // BPF_PROG_TYPE_XDP #include // XDP_FLAGS_DRV_MODE #include // sockaddr_xdp #include // if_nametoindex #include // mmap mode constants #include // poll #include // TODO: add support for multiple interfaces // Only relatively fresh kernels have this type and we need to declare this type on older kernels to be able to compile // libbpf On Ubuntu 20.04 and Debian 11 #ifdef DECLARE_FAKE_BPF_STATS /* type for BPF_ENABLE_STATS */ enum bpf_stats_type { /* enabled run_time_ns and run_cnt */ BPF_STATS_RUN_TIME = 0, }; #endif #ifdef DECLARE_FAKE_BPF_LINK_TYPE enum bpf_link_type { BPF_LINK_TYPE_UNSPEC = 0, BPF_LINK_TYPE_RAW_TRACEPOINT = 1, BPF_LINK_TYPE_TRACING = 2, BPF_LINK_TYPE_CGROUP = 3, BPF_LINK_TYPE_ITER = 4, BPF_LINK_TYPE_NETNS = 5, BPF_LINK_TYPE_XDP = 6, BPF_LINK_TYPE_PERF_EVENT = 7, MAX_BPF_LINK_TYPE, }; #endif extern "C" { #include #include } #include // RLIM_INFINITY // Our new generation parser #include "../simple_packet_parser_ng.hpp" #include "../fastnetmon_configuration_scheme.hpp" extern time_t current_inaccurate_time; // Global configuration map extern std::map configuration_map; extern fastnetmon_configuration_t fastnetmon_global_configuration; std::string packets_received_desc = "Total number of packets received by AF_XDP"; uint64_t packets_received = 0; std::string xdp_packets_unparsed_desc = "Total number of packets with parser issues. It may be broken packets or non IP traffic"; uint64_t xdp_packets_unparsed = 0; // Evern 4.19 kernel does not have this declaration in headers #ifndef AF_XDP #define AF_XDP 44 #endif #ifndef SOL_XDP #define SOL_XDP 283 #endif #define NUM_DESCS 1024 #define BATCH_SIZE 16 #define FILL_QUEUE_NUM_DESCS 1024 #define COMPLETION_QUEUE_NUM_DESCS 1024 #define NUM_FRAMES 131072 #define FRAME_SIZE 2048 // We do not need any headroom #define FRAME_HEADROOM 0 // clang-format off #define memory_barrier() __asm__ __volatile__("": : :"memory") // clang-format on process_packet_pointer xdp_process_func_ptr = nullptr; class xdp_umem_uqueue { public: __u32 cached_prod = 0; __u32 cached_cons = 0; __u32 mask = 0; __u32 size = 0; __u32* producer = nullptr; __u32* consumer = nullptr; __u64* ring = nullptr; void* map = nullptr; }; class xdp_uqueue { public: __u32 cached_prod = 0; __u32 cached_cons = 0; __u32 mask = 0; __u32 size = 0; __u32* producer = nullptr; __u32* consumer = nullptr; xdp_desc* ring = nullptr; void* map = nullptr; }; // Keeps all information about memory for AF_XDP socket class xsk_memory_configuration { public: xdp_umem_uqueue fill_queue{}; xdp_umem_uqueue completion_queue{}; char* buffer = nullptr; }; std::vector get_xdp_stats() { std::vector system_counter; system_counter.push_back(system_counter_t("xdp_packets_received", packets_received, metric_type_t::counter, packets_received_desc)); system_counter.push_back(system_counter_t("xdp_packets_unparsed", xdp_packets_unparsed, metric_type_t::counter, xdp_packets_unparsed_desc)); return system_counter; } // Creates memory region for XDP socket bool configure_memory_buffers(int xsk_handle, xsk_memory_configuration& memory_configuration) { int fill_queue_size = FILL_QUEUE_NUM_DESCS; int completion_queue_size = COMPLETION_QUEUE_NUM_DESCS; void* buffer = nullptr; size_t allocation_size = NUM_FRAMES * FRAME_SIZE; logger << log4cpp::Priority::INFO << "Allocating " << allocation_size << " bytes"; // Allocates aligned memory auto memalign_res = posix_memalign(&buffer, getpagesize(), allocation_size); if (memalign_res != 0) { logger << log4cpp::Priority::ERROR << "Cannot allocate memory. Error code: " << memalign_res; return false; } xdp_umem_reg umem_register{}; memset(&umem_register, 0, sizeof(xdp_umem_reg)); umem_register.addr = (__u64)buffer; umem_register.len = NUM_FRAMES * FRAME_SIZE; umem_register.chunk_size = FRAME_SIZE; umem_register.headroom = FRAME_HEADROOM; auto set_xdp_umem_reg = setsockopt(xsk_handle, SOL_XDP, XDP_UMEM_REG, &umem_register, sizeof(xdp_umem_reg)); if (set_xdp_umem_reg != 0) { logger << log4cpp::Priority::ERROR << "setsockopt failed for XDP_UMEM_REG code: " << set_xdp_umem_reg; return false; } auto set_umem_fill_ring = setsockopt(xsk_handle, SOL_XDP, XDP_UMEM_FILL_RING, &fill_queue_size, sizeof(int)); if (set_umem_fill_ring != 0) { logger << log4cpp::Priority::ERROR << "setsockopt failed for XDP_UMEM_FILL_RING code: " << set_umem_fill_ring; return false; } auto set_umem_completion_ring = setsockopt(xsk_handle, SOL_XDP, XDP_UMEM_COMPLETION_RING, &completion_queue_size, sizeof(int)); if (set_umem_completion_ring != 0) { logger << log4cpp::Priority::ERROR << "setsockopt failed for XDP_UMEM_COMPLETION_RING code: " << set_umem_completion_ring; return false; } xdp_mmap_offsets mmap_offset; memset(&mmap_offset, 0, sizeof(xdp_mmap_offsets)); socklen_t options_length = sizeof(xdp_mmap_offsets); auto set_mmap_offsets = getsockopt(xsk_handle, SOL_XDP, XDP_MMAP_OFFSETS, &mmap_offset, &options_length); if (set_mmap_offsets != 0) { logger << log4cpp::Priority::ERROR << "setsockopt failed for XDP_MMAP_OFFSETS code: " << set_mmap_offsets; return false; } // Configure fill queue xdp_umem_uqueue fill_queue_descriptor{}; fill_queue_descriptor.map = mmap(0, mmap_offset.fr.desc + FILL_QUEUE_NUM_DESCS * sizeof(__u64), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, xsk_handle, XDP_UMEM_PGOFF_FILL_RING); if (fill_queue_descriptor.map == MAP_FAILED) { logger << log4cpp::Priority::ERROR << "Fill queue mmap failed, error code: " << errno << " error: " << strerror(errno); return false; } fill_queue_descriptor.mask = FILL_QUEUE_NUM_DESCS - 1; fill_queue_descriptor.size = FILL_QUEUE_NUM_DESCS; fill_queue_descriptor.producer = (__u32*)((unsigned char*)fill_queue_descriptor.map + mmap_offset.fr.producer); fill_queue_descriptor.consumer = (__u32*)((unsigned char*)fill_queue_descriptor.map + mmap_offset.fr.consumer); fill_queue_descriptor.ring = (__u64*)((unsigned char*)fill_queue_descriptor.map + mmap_offset.fr.desc); fill_queue_descriptor.cached_cons = FILL_QUEUE_NUM_DESCS; // Configure completion queue xdp_umem_uqueue completion_queue_descriptor{}; completion_queue_descriptor.map = mmap(0, mmap_offset.cr.desc + COMPLETION_QUEUE_NUM_DESCS * sizeof(__u64), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, xsk_handle, XDP_UMEM_PGOFF_COMPLETION_RING); if (completion_queue_descriptor.map == MAP_FAILED) { logger << log4cpp::Priority::ERROR << "Completion queue mmap failed, error code: " << errno << " error: " << strerror(errno); return false; } completion_queue_descriptor.mask = COMPLETION_QUEUE_NUM_DESCS - 1; completion_queue_descriptor.size = COMPLETION_QUEUE_NUM_DESCS; completion_queue_descriptor.producer = (__u32*)((unsigned char*)completion_queue_descriptor.map + mmap_offset.cr.producer); completion_queue_descriptor.consumer = (__u32*)((unsigned char*)completion_queue_descriptor.map + mmap_offset.cr.consumer); completion_queue_descriptor.ring = (__u64*)((unsigned char*)completion_queue_descriptor.map + mmap_offset.cr.desc); memory_configuration.fill_queue = fill_queue_descriptor; memory_configuration.completion_queue = completion_queue_descriptor; memory_configuration.buffer = (char*)buffer; return true; } uint32_t u_memory_nb_free(xdp_umem_uqueue* queue, uint32_t nb) { uint32_t free_entries = queue->cached_cons - queue->cached_prod; if (free_entries >= nb) { return free_entries; } /* Refresh reference */ queue->cached_cons = *queue->consumer + queue->size; return queue->cached_cons - queue->cached_prod; } bool execute_fill_to_kernel(xdp_umem_uqueue* fill_queue, xdp_desc* desc, unsigned int number_of_packets) { auto free_entries = u_memory_nb_free(fill_queue, number_of_packets); if (free_entries < number_of_packets) { return false; } for (int i = 0; i < number_of_packets; i++) { uint32_t index = fill_queue->cached_prod++ & fill_queue->mask; fill_queue->ring[index] = desc[i].addr; } memory_barrier(); *fill_queue->producer = fill_queue->cached_prod; return true; } bool execute_initial_memfill(xdp_umem_uqueue* fill_queue, int* d, size_t nb) { auto free_entries = u_memory_nb_free(fill_queue, nb); if (free_entries < nb) { return false; } for (int i = 0; i < nb; i++) { uint32_t index = fill_queue->cached_prod++ & fill_queue->mask; fill_queue->ring[index] = d[i]; } memory_barrier(); *fill_queue->producer = fill_queue->cached_prod; return true; } // Creates and configures XSK socket bool create_and_configure_xsk_socket(int& xsk_socket_param, unsigned int ifindex, int queue_id, xsk_memory_configuration& mem_conf_param, xdp_uqueue& rx) { int xsk_handle = socket(AF_XDP, SOCK_RAW, 0); if (xsk_handle == -1) { logger << log4cpp::Priority::ERROR << "Cannot create socket. Error code: " << errno << " error: " << strerror(errno); return false; } // Allocate memory for buffers xsk_memory_configuration memory_configuration{}; bool memory_configuration_res = configure_memory_buffers(xsk_handle, memory_configuration); if (!memory_configuration_res) { return false; } int number_of_descriptors = NUM_DESCS; auto set_rx_rings = setsockopt(xsk_handle, SOL_XDP, XDP_RX_RING, &number_of_descriptors, sizeof(int)); if (set_rx_rings != 0) { logger << log4cpp::Priority::ERROR << "Cannot set number of RX rings"; return false; } auto set_tx_rings = setsockopt(xsk_handle, SOL_XDP, XDP_TX_RING, &number_of_descriptors, sizeof(int)); if (set_tx_rings != 0) { logger << log4cpp::Priority::ERROR << "Cannot set number of TX rings"; return false; } xdp_mmap_offsets mmap_offset; memset(&mmap_offset, 0, sizeof(xdp_mmap_offsets)); socklen_t optlen = sizeof(xdp_mmap_offsets); auto get_mmap_ffsets_res = getsockopt(xsk_handle, SOL_XDP, XDP_MMAP_OFFSETS, &mmap_offset, &optlen); if (get_mmap_ffsets_res != 0) { logger << log4cpp::Priority::ERROR << "Cannot get XDP mmap offsets"; return false; } // Return socket to caller xsk_socket_param = xsk_handle; rx.map = mmap(NULL, mmap_offset.rx.desc + NUM_DESCS * sizeof(xdp_desc), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, xsk_handle, XDP_PGOFF_RX_RING); if (rx.map == MAP_FAILED) { logger << log4cpp::Priority::ERROR << "Cannot mmap RX for XDP socket"; return false; } for (int i = 0; i < NUM_DESCS * FRAME_SIZE; i += FRAME_SIZE) { auto memfill_res = execute_initial_memfill(&memory_configuration.fill_queue, &i, 1); if (!memfill_res) { logger << log4cpp::Priority::ERROR << "Cannot execute initial memory filling"; return false; } } rx.mask = NUM_DESCS - 1; rx.size = NUM_DESCS; rx.producer = (__u32*)((unsigned char*)rx.map + mmap_offset.rx.producer); rx.consumer = (__u32*)((unsigned char*)rx.map + mmap_offset.rx.consumer); rx.ring = (xdp_desc*)((unsigned char*)rx.map + mmap_offset.rx.desc); sockaddr_xdp sockaddr_xdp_descriptor; memset(&sockaddr_xdp_descriptor, 0, sizeof(sockaddr_xdp)); sockaddr_xdp sxdp; memset(&sxdp, 0, sizeof(sockaddr_xdp)); sockaddr_xdp_descriptor.sxdp_family = AF_XDP; sockaddr_xdp_descriptor.sxdp_ifindex = ifindex; sockaddr_xdp_descriptor.sxdp_queue_id = queue_id; __u32 bind_flags = 0; bool force_native_mode_xdp = configuration_map["force_native_mode_xdp"] == "on"; bool zero_copy_xdp = configuration_map["zero_copy_xdp"] == "on"; if (!force_native_mode_xdp) { // In copy mode we need one more additional option for bind process bind_flags |= XDP_COPY; } else { // For native mode we can enable ZERO COPY mode when customer requested it if (zero_copy_xdp) { bind_flags |= XDP_ZEROCOPY; } } sockaddr_xdp_descriptor.sxdp_flags = bind_flags; int bind_res = bind(xsk_handle, (sockaddr*)&sockaddr_xdp_descriptor, sizeof(sockaddr_xdp_descriptor)); if (bind_res) { logger << log4cpp::Priority::ERROR << "Cannot bind to socket with error code " << errno << " error: " << strerror(errno); return false; } logger << log4cpp::Priority::INFO << "Correctly bind socket"; // Return memory configuration to caller mem_conf_param = memory_configuration; return true; } // Returns true if we have any packets for processing unsigned int packets_available(xdp_uqueue* rx, int number_of_descriptors) { auto entries = rx->cached_prod - rx->cached_cons; if (entries == 0) { rx->cached_prod = *rx->producer; entries = rx->cached_prod - rx->cached_cons; } if (entries > number_of_descriptors) { return number_of_descriptors; } else { return entries; } } unsigned int dequeue_packets(xdp_uqueue* rx, xdp_desc* descs, int number_of_descriptors) { int entries = packets_available(rx, number_of_descriptors); xdp_desc* r = rx->ring; memory_barrier(); for (int i = 0; i < entries; i++) { unsigned int idx = rx->cached_cons++ & rx->mask; descs[i] = r[idx]; } if (entries > 0) { memory_barrier(); *rx->consumer = rx->cached_cons; } return entries; } void xdp_process_traffic(int xdp_socket, xsk_memory_configuration* mem_configuration, xdp_uqueue* rx) { logger << log4cpp::Priority::INFO << "Start traffic processing"; // Create structures for poll syscall pollfd monitored_fds[1]; memset(monitored_fds, 0, sizeof(pollfd)); monitored_fds[0].fd = xdp_socket; monitored_fds[0].events = POLLIN; // Timeout in milliseconds int timeout_poll = 1000; nfds_t number_of_monitored_fds = 1; bool poll_mode_xdp = configuration_map["poll_mode_xdp"] == "on"; bool xdp_read_packet_length_from_ip_header = configuration_map["xdp_read_packet_length_from_ip_header"] == "on"; while (true) { if (poll_mode_xdp) { int poll_res = poll(monitored_fds, number_of_monitored_fds, timeout_poll); if (poll_res == 0) { // Timeout happened logger << log4cpp::Priority::DEBUG << "Timeout happened"; continue; } else if (poll_res < 0) { // Error happened logger << log4cpp::Priority::ERROR << "Error during poll happened. Error code: " << errno << " error: " << strerror(errno); continue; } else { // We got some data! } } xdp_desc descs[BATCH_SIZE]; unsigned int received = dequeue_packets(rx, descs, BATCH_SIZE); if (received == 0) { continue; } // Iterate over all packets for (unsigned int i = 0; i < received; i++) { void* packet_data = &mem_configuration->buffer[descs[i].addr]; simple_packet_t packet; packet.source = MIRROR; packet.arrival_time = current_inaccurate_time; parser_options_t parser_options{}; parser_options.unpack_gre = false; parser_options.read_packet_length_from_ip_header = xdp_read_packet_length_from_ip_header; auto result = parse_raw_packet_to_simple_packet_full_ng((u_char*)packet_data, descs[i].len, descs[i].len, packet, parser_options); if (result != network_data_stuctures::parser_code_t::success) { xdp_packets_unparsed++; logger << log4cpp::Priority::DEBUG << "Cannot parse packet using ng parser: " << network_data_stuctures::parser_code_to_string(result); } else { // Successfully parsed packet xdp_process_func_ptr(packet); } } execute_fill_to_kernel(&mem_configuration->fill_queue, descs, received); } } void start_xdp_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "XDP plugin started"; std::vector interfaces_xdp; if (configuration_map.count("interfaces") != 0) { boost::split(interfaces_xdp, configuration_map["interfaces"], boost::is_any_of(","), boost::token_compress_on); } if (interfaces_xdp.size() == 0) { logger << log4cpp::Priority::ERROR << "Please specify interface for XDP"; return; } xdp_process_func_ptr = func_ptr; // We should increase this limit because default one causes bpf map failures: // https://patchwork.ozlabs.org/patch/831562/ rlimit rlimit_infinity = { RLIM_INFINITY, RLIM_INFINITY }; if (setrlimit(RLIMIT_MEMLOCK, &rlimit_infinity) != 0) { logger << log4cpp::Priority::ERROR << "Cannot set rlimit memlock with error " << strerror(errno); return; } // TODO: move it to resources or expose configuration option std::string bpf_microcode_path = configuration_map["microcode_xdp_path"]; if (!file_exists(bpf_microcode_path)) { logger << log4cpp::Priority::ERROR << "Specified microcode path " << bpf_microcode_path << " does not exist"; return; } bpf_object* obj = bpf_object__open_file(bpf_microcode_path.c_str(), NULL); int open_file_error_code = libbpf_get_error(obj); if (open_file_error_code) { // Documentation claims https://libbpf.readthedocs.io/en/latest/api.html that errno will be set too logger << log4cpp::Priority::ERROR << "Cannot open BPF file: " << bpf_microcode_path << " with error code " << open_file_error_code << " errno " << errno; return; } bpf_program* prog = bpf_object__next_program(obj, NULL); bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); int bpf_load_res = bpf_object__load(obj); if (bpf_load_res != 0) { logger << log4cpp::Priority::ERROR << "Cannot load BPF microcode code: " << bpf_load_res; return; } int prog_fd = bpf_program__fd(prog); if (prog_fd < 0) { logger << log4cpp::Priority::ERROR << "No BPF program found"; return; } // Lookup queue configuration map bpf_map* queue_map = bpf_object__find_map_by_name(obj, "qidconf_map"); int qidconf_map = bpf_map__fd(queue_map); if (qidconf_map < 0) { logger << log4cpp::Priority::ERROR << "Cannot find queue configuration map"; return; } // TODO: make it configurable int queue_id = 0; // Lookup XSK map bpf_map* xsk_map = bpf_object__find_map_by_name(obj, "xsks_map"); int xsks_map = bpf_map__fd(xsk_map); if (xsks_map < 0) { logger << log4cpp::Priority::ERROR << "Cannot find XSP socket map"; return; } std::string interface = interfaces_xdp[0]; logger << log4cpp::Priority::INFO << "We support only single interface and will use " << interface; bool xdp_set_promisc = configuration_map["xdp_set_promisc"] == "on"; // We should set interface to promisc mode because AF_XDP does not do it for us if (xdp_set_promisc) { manage_interface_promisc_mode(interface, true); } unsigned int ifindex = if_nametoindex(interface.c_str()); if (ifindex == 0) { logger << log4cpp::Priority::ERROR << "Cannot get interface handler for " << interface << " error code " << errno << " error: " << strerror(errno); return; } __u32 opt_xdp_flags = 0; bool force_native_mode_xdp = configuration_map["force_native_mode_xdp"] == "on"; if (force_native_mode_xdp) { opt_xdp_flags |= XDP_FLAGS_DRV_MODE; logger << log4cpp::Priority::INFO << "Will use native XDP mode"; } else { opt_xdp_flags |= XDP_FLAGS_SKB_MODE; logger << log4cpp::Priority::INFO << "Will use copy/generic XDP mode"; } // In version 1.x they've removed old interface completely // We keep this code only for EPEL 9 compatibility #if LIBBPF_MAJOR_VERSION > 0 int set_link_xdp_res = bpf_xdp_attach(ifindex, prog_fd, opt_xdp_flags, NULL); #else int set_link_xdp_res = bpf_set_link_xdp_fd(ifindex, prog_fd, opt_xdp_flags); #endif if (set_link_xdp_res < 0) { // Get human friendly code char buf[1024]; libbpf_strerror(set_link_xdp_res, buf, 1024); logger << log4cpp::Priority::ERROR << "Cannot assign BPF microcode to interface " << interface << " error code: " << set_link_xdp_res << " error: " << buf; return; } int queue_key = 0; int ret_update_map = bpf_map_update_elem(qidconf_map, &queue_key, &queue_id, 0); if (ret_update_map != 0) { logger << log4cpp::Priority::ERROR << "Cannot update queue configuration map"; return; } // Create socket int xsk_socket = 0; xsk_memory_configuration mem_configuration{}; xdp_uqueue rx{}; auto socket_res = create_and_configure_xsk_socket(xsk_socket, ifindex, queue_id, mem_configuration, rx); if (!socket_res) { logger << log4cpp::Priority::ERROR << "Cannot configure socket"; return; } logger << log4cpp::Priority::INFO << "Correctly created socket: " << xsk_socket; // Let's add our AF_XDP socket as consumer for this XDP microcode int socket_map_key = 0; auto xsk_map_update_res = bpf_map_update_elem(xsks_map, &socket_map_key, &xsk_socket, 0); if (xsk_map_update_res != 0) { logger << log4cpp::Priority::ERROR << "Cannot update socket configuration map"; return; } xdp_process_traffic(xsk_socket, &mem_configuration, &rx); } upstream-fastnetmon/src/xdp_plugin/xdp_collector.hpp0000664000175000017500000000023515060514305021171 0ustar meme#pragma once #include "../fastnetmon_types.hpp" void start_xdp_collection(process_packet_pointer func_ptr); std::vector get_xdp_stats(); upstream-fastnetmon/src/xdp_plugin/xdp_kernel.c0000664000175000017500000000233615060514305020122 0ustar meme// SPDX-License-Identifier: GPL-2.0 #define KBUILD_MODNAME "foo" #include #include #include // // To compile it on Ubuntu 22.04 x86_64 you will need following packages: // sudo apt install -y clang libbpf-dev gcc-multilib // // Sadly ARM64 has no gcc-multilib package and we cannot compile it on ARM64 boxes // // Compile command: // // clang -c -g -O2 -target bpf xdp_kernel.c -o xdp_kernel.o // // To unload BPF for specific interface you need to apply following command: // // sudo xdp-loader unload --all // struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 1); __type(key, int); __type(value, int); } qidconf_map SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_XSKMAP); __uint(max_entries, 4); __type(key, int); __type(value, int); } xsks_map SEC(".maps"); SEC("xdp_sock") int xdp_sock_prog(struct xdp_md* ctx) { int *qidconf, key = 0, idx; unsigned int* rr; qidconf = bpf_map_lookup_elem(&qidconf_map, &key); if (!qidconf) return XDP_ABORTED; if (*qidconf != ctx->rx_queue_index) return XDP_PASS; idx = 0; return bpf_redirect_map(&xsks_map, idx, 0); } char _license[] SEC("license") = "GPL"; upstream-fastnetmon/src/packaging/0000775000175000017500000000000015060514305015372 5ustar memeupstream-fastnetmon/src/packaging/fedora/0000775000175000017500000000000015060514305016632 5ustar memeupstream-fastnetmon/src/packaging/fedora/README.md0000664000175000017500000000015315060514305020110 0ustar memeYou can find up to date SPECs for Fedora on [upstream page](https://src.fedoraproject.org/rpms/fastnetmon) upstream-fastnetmon/src/packaging/fedora/fastnetmon.spec0000664000175000017500000000605715060514305021674 0ustar meme%global fastnetmon_attackdir %{_localstatedir}/log/fastnetmon_attacks %global fastnetmon_user fastnetmon %global fastnetmon_group %{fastnetmon_user} %global fastnetmon_config_path %{_sysconfdir}/fastnetmon.conf %global commit0 master Name: fastnetmon Version: 1.2.2 Release: 1.git%{commit0}%{?dist} Summary: DDoS detection tool with sFlow, Netflow, IPFIX and port mirror support License: GPLv2 URL: https://github.com/pavel-odintsov/fastnetmon Source0: https://github.com/pavel-odintsov/fastnetmon/archive/%{commit0}.tar.gz Source1: fastnetmon.sysusers BuildRequires: make BuildRequires: gcc BuildRequires: gcc-c++ BuildRequires: boost-devel BuildRequires: log4cpp-devel BuildRequires: ncurses-devel BuildRequires: boost-thread BuildRequires: boost-regex BuildRequires: libpcap-devel BuildRequires: gpm-devel BuildRequires: cmake BuildRequires: capnproto-devel BuildRequires: capnproto BuildRequires: grpc-devel BuildRequires: grpc-cpp BuildRequires: abseil-cpp-devel BuildRequires: grpc-plugins BuildRequires: mongo-c-driver-devel BuildRequires: json-c-devel BuildRequires: libbpf-devel BuildRequires: systemd BuildRequires: systemd-rpm-macros Requires(pre): shadow-utils %{?systemd_requires} %description DDoS detection tool with sFlow, Netflow, IPFIX and port mirror support. %prep %autosetup -n %{name}-%{commit0} %build %cmake -DCMAKE_SKIP_BUILD_RPATH=TRUE -DLINK_WITH_ABSL=TRUE -S src %cmake_build %install # install systemd unit file install -p -D -m 0644 src/packaging/fedora/fastnetmon.service %{buildroot}%{_unitdir}/fastnetmon.service # install daemon binary install -p -D -m 0755 %__cmake_builddir/fastnetmon %{buildroot}%{_sbindir}/fastnetmon # install client binary install -p -D -m 0755 %__cmake_builddir/fastnetmon_client %{buildroot}%{_bindir}/fastnetmon_client # install api client binary install -p -D -m 0755 %__cmake_builddir/fastnetmon_api_client %{buildroot}%{_bindir}/fastnetmon_api_client # install config install -p -D -m 0644 src/fastnetmon.conf %{buildroot}%{fastnetmon_config_path} # Create log folder install -p -d -m 0700 %{buildroot}%{fastnetmon_attackdir} # Create sysuser manifest to create dynamic user for us install -D -p -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/fastnetmon.conf %pre %sysusers_create_compat %{SOURCE1} %post %systemd_post fastnetmon.service %preun %systemd_preun fastnetmon.service %postun %systemd_postun_with_restart fastnetmon.service %files %{_unitdir}/fastnetmon.service %{_sysusersdir}/fastnetmon.conf # Binary daemon %{_sbindir}/fastnetmon %{_bindir}/fastnetmon_client %{_bindir}/fastnetmon_api_client %config(noreplace) %{fastnetmon_config_path} %attr(700,%{fastnetmon_user},%{fastnetmon_group}) %dir %{fastnetmon_attackdir} %license LICENSE %doc README.md SECURITY.md THANKS.md %changelog * Sat May 28 2022 Pavel Odintsov - 1.2.1-1.20220528git420e7b8 - First RPM package release upstream-fastnetmon/src/packaging/fedora/fastnetmon.sysusers0000664000175000017500000000010515060514305022626 0ustar memeu fastnetmon - "FastNetMon system user" - /sbin/nologin upstream-fastnetmon/src/packaging/fedora/fastnetmon.service0000664000175000017500000000071615060514305022376 0ustar meme[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=/usr/sbin/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 upstream-fastnetmon/src/packaging/FreeBSD/0000775000175000017500000000000015060514305016604 5ustar memeupstream-fastnetmon/src/packaging/FreeBSD/Makefile0000664000175000017500000000412415060514305020245 0ustar memePORTNAME= fastnetmon PORTVERSION= 1.2.4 DISTVERSIONPREFIX= v CATEGORIES= net-mgmt security MAINTAINER= farrokhi@FreeBSD.org COMMENT= Very fast DDoS analyzer with sflow/netflow/mirror support WWW= https://github.com/pavel-odintsov/fastnetmon LICENSE= GPLv2 LICENSE_FILE= ${WRKSRC}/LICENSE BROKEN_armv6= does not build: invokes x86 assembler BROKEN_armv7= does not build: invokes x86 assembler LIB_DEPENDS= libboost_regex.so:devel/boost-libs \ liblog4cpp.so:devel/log4cpp \ libmongoc-1.0.so:devel/mongo-c-driver \ libjson-c.so:devel/json-c \ libbson-1.0.so:devel/libbson \ libcapnp.so:devel/capnproto \ libgrpc.so:devel/grpc142 \ libprotobuf.so:devel/protobuf \ libabsl_base.so:devel/abseil \ libhiredis.so:databases/hiredis USES= cmake compiler:c++11-lang pkgconfig localbase:ldflags ssl USE_GITHUB= yes GH_ACCOUNT= pavel-odintsov USERS= ${PORTNAME} GROUPS= ${PORTNAME} USE_RC_SUBR= ${PORTNAME} OPTIONS_DEFINE= DOCS REDIS REDIS_LIB_DEPENDS= libhiredis.so:databases/hiredis PORTDOCS= * CXXFLAGS += -DBOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED CFLAGS_i386= -march=i586 CMAKE_SOURCE_PATH= ${WRKSRC}/src CMAKE_ARGS+= -DDISABLE_PF_RING_SUPPORT=ON \ -DENABLE_NETMAP_SUPPORT=OFF \ -DLINK_WITH_ABSL=ON \ -DSET_ABSOLUTE_INSTALL_PATH=OFF \ -DCMAKE_INSTALL_MANDIR=${PREFIX}/man CMAKE_INSTALL_PREFIX= ${PREFIX} .include post-patch: @${REINPLACE_CMD} -e 's|/usr/local|${PREFIX}|; \ s|/var/run|&/fastnetmon|g; s|/var/log|&/fastnetmon|g; \ s|"/etc/|"${PREFIX}/etc/|g; s|/root/fastnetmon|${DATADIR}|g' \ ${WRKSRC}/src/fastnetmon.conf ${WRKSRC}/src/fastnetmon.cpp @${REINPLACE_CMD} -e 's|%%PREFIX%%|${PREFIX}|g' \ ${WRKSRC}/src/man/fastnetmon.8 ${WRKSRC}/src/fast_platform.h.template post-install: ${MV} ${STAGEDIR}${PREFIX}/etc/${PORTNAME}.conf \ ${STAGEDIR}${PREFIX}/etc/${PORTNAME}.conf.sample ${MKDIR} ${STAGEDIR}/var/run/fastnetmon ${STAGEDIR}/var/log/fastnetmon ${INSTALL_SCRIPT} ${WRKSRC}/src/notify_about_attack.sh ${STAGEDIR}${PREFIX}/bin post-install-DOCS-on: cd ${WRKSRC} && ${COPYTREE_SHARE} "README.md docs" ${STAGEDIR}${DOCSDIR} .include upstream-fastnetmon/src/packaging/FreeBSD/files/0000775000175000017500000000000015060514305017706 5ustar memeupstream-fastnetmon/src/packaging/FreeBSD/files/patch-src_man_fastnetmon.80000664000175000017500000000143215060514305024754 0ustar meme--- src/man/fastnetmon.8.orig 2023-03-03 16:20:18 UTC +++ src/man/fastnetmon.8 @@ -8,7 +8,7 @@ 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. +For more information about configuration, please look at the comments in %%PREFIX%%/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 upstream-fastnetmon/src/packaging/FreeBSD/files/fastnetmon.in0000664000175000017500000000116715060514305022421 0ustar meme#!/bin/sh # PROVIDE: fastnetmon # REQUIRE: NETWORKING SERVERS LOGIN # BEFORE: securelevel # KEYWORD: shutdown # Add the following line to /etc/rc.conf to enable 'fastnetmon': # # fastnetmon_enable="YES" # . /etc/rc.subr name=fastnetmon desc="fastnetmon startup script" rcvar=fastnetmon_enable load_rc_config "$name" : ${fastnetmon_enable:=NO} : ${fastnetmon_conf:=%%ETCDIR%%/$name.conf} : ${fastnetmon_flags:=--daemonize} : ${fastnetmon_user:=fastnetmon} command=%%PREFIX%%/bin/fastnetmon command_args="--configuration_file ${fastnetmon_conf} ${fastnetmon_flags}" pidfile=/var/run/fastnetmon/$name.pid run_rc_command "$1" upstream-fastnetmon/src/packaging/FreeBSD/files/patch-src_fast__library.cpp0000664000175000017500000000075615060514305025206 0ustar meme--- src/fast_library.cpp.orig 2023-03-05 11:34:07 UTC +++ src/fast_library.cpp @@ -1249,7 +1249,11 @@ bool get_interface_number_by_device_name(int socket_fd return false; } - interface_number = ifr.ifr_ifindex; + #ifdef __FreeBSD__ + interface_number = ifr.ifr_ifru.ifru_index; + #else + interface_number = ifr.ifr_ifindex; + #endif #else /* Fallback to if_nametoindex(3) otherwise. */ interface_number = if_nametoindex(interface_name.c_str()); upstream-fastnetmon/src/packaging/FreeBSD/files/patch-src_fast__platform.h.template0000664000175000017500000000223115060514305026633 0ustar meme--- src/fast_platform.h.template.orig 2023-03-01 14:23:34 UTC +++ src/fast_platform.h.template @@ -9,19 +9,19 @@ class FastnetmonPlatformConfigurtion { std::string fastnetmon_version = "${FASTNETMON_APPLICATION_VERSION}"; std::string pid_path = "/var/run/fastnetmon.pid"; - std::string global_config_path = "/etc/fastnetmon.conf"; + std::string global_config_path = "%%PREFIX%%/etc/fastnetmon.conf"; std::string log_file_path = "/var/log/fastnetmon.log"; std::string attack_details_folder = "/var/log/fastnetmon_attacks"; // Default path to notify script - std::string notify_script_path = "/usr/local/bin/notify_about_attack.sh"; + std::string notify_script_path = "%%PREFIX%%/bin/notify_about_attack.sh"; // Default path to file with networks for whitelising - std::string white_list_path = "/etc/networks_whitelist"; + std::string white_list_path = "%%PREFIX%%/etc/networks_whitelist"; // Default path to file with all networks listing - std::string networks_list_path = "/etc/networks_list"; + std::string networks_list_path = "%%PREFIX%%/etc/networks_list"; /* Platform specific paths end */ }; upstream-fastnetmon/src/packaging/FreeBSD/files/patch-src_notify__about__attack.sh0000664000175000017500000000032715060514305026537 0ustar meme--- src/notify_about_attack.sh.orig 2023-03-06 10:33:26 UTC +++ src/notify_about_attack.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh # # Hello, lovely FastNetMon customer. I'm really happy to see you here upstream-fastnetmon/src/packaging/FreeBSD/files/patch-src_fastnetmon.cpp0000664000175000017500000000277615060514305024550 0ustar meme--- src/fastnetmon.cpp.orig 2023-03-01 14:23:34 UTC +++ src/fastnetmon.cpp @@ -12,8 +12,8 @@ #include #include -#include // struct arphdr -#include +//#include // struct arphdr +//#include #include #include #include @@ -161,7 +161,7 @@ unsigned int stats_thread_initial_call_delay = 30; std::string reporting_server = "community-stats.pavel-odintsov.com"; // Path to temporarily store backtrace when fatal failure happened -std::string backtrace_path = "/var/log/fastnetmon_backtrace.dump"; +std::string backtrace_path = "/var/log/fastnetmon/fastnetmon_backtrace.dump"; // Each this seconds we will check about available data in bucket unsigned int check_for_availible_for_processing_packets_buckets = 1; @@ -435,7 +435,7 @@ std::string exabgp_community_subnet = ""; std::string exabgp_community_host = ""; -std::string exabgp_command_pipe = "/var/run/exabgp.cmd"; +std::string exabgp_command_pipe = "/var/run/fastnetmon/exabgp.cmd"; std::string exabgp_next_hop = ""; // Graphite monitoring @@ -519,7 +519,7 @@ void sigpipe_handler_for_popen(int signo) { #ifdef GEOIP bool geoip_init() { // load GeoIP ASN database to memory - geo_ip = GeoIP_open("/root/fastnetmon/GeoIPASNum.dat", GEOIP_MEMORY_CACHE); + geo_ip = GeoIP_open("/usr/local/share/fastnetmon/GeoIPASNum.dat", GEOIP_MEMORY_CACHE); if (geo_ip == NULL) { return false; upstream-fastnetmon/src/packaging/FreeBSD/files/patch-src_fast__endianless.hpp0000664000175000017500000000053615060514305025670 0ustar meme--- src/fast_endianless.hpp.orig 2023-03-04 15:33:46 UTC +++ src/fast_endianless.hpp @@ -12,6 +12,7 @@ // For be64toh and htobe64 #if defined(__FreeBSD__) || defined(__DragonFly__) #include +#include #endif // Linux standard functions for endian conversions are ugly because there are no checks about arguments length upstream-fastnetmon/src/packaging/FreeBSD/pkg-plist0000664000175000017500000000047715060514305020451 0ustar memebin/fastnetmon bin/fastnetmon_api_client bin/fastnetmon_client bin/notify_about_attack.sh etc/networks_list etc/networks_whitelist man/man8/fastnetmon.8.gz man/man1/fastnetmon_client.1.gz @sample etc/fastnetmon.conf.sample @dir(fastnetmon,fastnetmon) /var/run/fastnetmon @dir(fastnetmon,fastnetmon) /var/log/fastnetmon upstream-fastnetmon/src/packaging/FreeBSD/pkg-descr0000664000175000017500000000022715060514305020407 0ustar memeFastNetMon - A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). upstream-fastnetmon/src/packaging/FreeBSD/distinfo0000664000175000017500000000031415060514305020344 0ustar memeTIMESTAMP = 1677848493 SHA256 (pavel-odintsov-fastnetmon-v1.2.4_GH0.tar.gz) = 84cd5db0e270f6c268923592eabd5cb0d1689293d9d9f6f0634af548b29f9bb4 SIZE (pavel-odintsov-fastnetmon-v1.2.4_GH0.tar.gz) = 1056097 upstream-fastnetmon/src/packaging/epel/0000775000175000017500000000000015060514305016317 5ustar memeupstream-fastnetmon/src/packaging/epel/fastnetmon.spec0000664000175000017500000000605715060514305021361 0ustar meme%global fastnetmon_attackdir %{_localstatedir}/log/fastnetmon_attacks %global fastnetmon_user fastnetmon %global fastnetmon_group %{fastnetmon_user} %global fastnetmon_config_path %{_sysconfdir}/fastnetmon.conf %global commit0 master Name: fastnetmon Version: 1.2.2 Release: 1.git%{commit0}%{?dist} Summary: DDoS detection tool with sFlow, Netflow, IPFIX and port mirror support License: GPLv2 URL: https://github.com/pavel-odintsov/fastnetmon Source0: https://github.com/pavel-odintsov/fastnetmon/archive/%{commit0}.tar.gz Source1: fastnetmon.sysusers BuildRequires: make BuildRequires: gcc BuildRequires: gcc-c++ BuildRequires: boost-devel BuildRequires: log4cpp-devel BuildRequires: ncurses-devel BuildRequires: boost-thread BuildRequires: boost-regex BuildRequires: libpcap-devel BuildRequires: gpm-devel BuildRequires: cmake BuildRequires: capnproto-devel BuildRequires: capnproto BuildRequires: grpc-devel BuildRequires: grpc-cpp BuildRequires: abseil-cpp-devel BuildRequires: grpc-plugins BuildRequires: mongo-c-driver-devel BuildRequires: json-c-devel BuildRequires: libbpf-devel BuildRequires: systemd BuildRequires: systemd-rpm-macros Requires(pre): shadow-utils %{?systemd_requires} %description DDoS detection tool with sFlow, Netflow, IPFIX and port mirror support. %prep %autosetup -n %{name}-%{commit0} %build %cmake -DCMAKE_SKIP_BUILD_RPATH=TRUE -DLINK_WITH_ABSL=TRUE -S src %cmake_build %install # install systemd unit file install -p -D -m 0644 src/packaging/fedora/fastnetmon.service %{buildroot}%{_unitdir}/fastnetmon.service # install daemon binary install -p -D -m 0755 %__cmake_builddir/fastnetmon %{buildroot}%{_sbindir}/fastnetmon # install client binary install -p -D -m 0755 %__cmake_builddir/fastnetmon_client %{buildroot}%{_bindir}/fastnetmon_client # install api client binary install -p -D -m 0755 %__cmake_builddir/fastnetmon_api_client %{buildroot}%{_bindir}/fastnetmon_api_client # install config install -p -D -m 0644 src/fastnetmon.conf %{buildroot}%{fastnetmon_config_path} # Create log folder install -p -d -m 0700 %{buildroot}%{fastnetmon_attackdir} # Create sysuser manifest to create dynamic user for us install -D -p -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/fastnetmon.conf %pre %sysusers_create_compat %{SOURCE1} %post %systemd_post fastnetmon.service %preun %systemd_preun fastnetmon.service %postun %systemd_postun_with_restart fastnetmon.service %files %{_unitdir}/fastnetmon.service %{_sysusersdir}/fastnetmon.conf # Binary daemon %{_sbindir}/fastnetmon %{_bindir}/fastnetmon_client %{_bindir}/fastnetmon_api_client %config(noreplace) %{fastnetmon_config_path} %attr(700,%{fastnetmon_user},%{fastnetmon_group}) %dir %{fastnetmon_attackdir} %license LICENSE %doc README.md SECURITY.md THANKS.md %changelog * Sat May 28 2022 Pavel Odintsov - 1.2.1-1.20220528git420e7b8 - First RPM package release upstream-fastnetmon/src/packaging/homebrew/0000775000175000017500000000000015060514305017202 5ustar memeupstream-fastnetmon/src/packaging/homebrew/fastnetmon.rb0000664000175000017500000000601715060514305021711 0ustar memeclass Fastnetmon < Formula desc "DDoS detection tool with sFlow, Netflow, IPFIX and port mirror support" homepage "https://github.com/pavel-odintsov/fastnetmon/" license "GPL-2.0-only" head "https://github.com/pavel-odintsov/fastnetmon.git" revision 5 bottle do sha256 cellar: :any, arm64_sonoma: "e2224278e6a039eb8815b070dbec167059c97c45ce140d040e743933551f4aa6" sha256 cellar: :any, arm64_ventura: "867ad398c827b030e7a37c6ae7ec43424b5f39049712ad4e73ad9f34272af6d8" sha256 cellar: :any, arm64_monterey: "289219de6117e93818efe215608ec63a581f65a844796296b8c8dcf7131e4282" sha256 cellar: :any, sonoma: "4e2b2eaae5b1208543fd6cba7d17319c61dca90694f353ae68271677da81b0a5" sha256 cellar: :any, ventura: "ca02b5e0eaf4c162a32529427ee82f9ce322dfd76f74da6da5f217ff3d0e733d" sha256 cellar: :any, monterey: "c69862f99e4e368ff2df5ffabdb74b04c415b8441f22ea7fdd2cb1bab24f1e57" sha256 cellar: :any_skip_relocation, x86_64_linux: "3e789634cedde18c8806a0d23a9469f72bc0f95da40f5e5556457f8a54425d9f" end depends_on "cmake" => :build depends_on "abseil" depends_on "boost" depends_on "capnp" depends_on "grpc" depends_on "hiredis" depends_on "log4cpp" depends_on macos: :big_sur # We need C++ 20 available for build which is available from Big Sur depends_on "mongo-c-driver" depends_on "openssl@3" depends_on "protobuf" uses_from_macos "ncurses" on_linux do depends_on "elfutils" depends_on "libbpf" depends_on "libpcap" end fails_with gcc: "5" def install system "cmake", "-S", "src", "-B", "build", "-DLINK_WITH_ABSL=TRUE", "-DSET_ABSOLUTE_INSTALL_PATH=OFF", *std_cmake_args system "cmake", "--build", "build" system "cmake", "--install", "build" end service do run [ opt_sbin/"fastnetmon", "--configuration_file", etc/"fastnetmon.conf", "--log_to_console" ] keep_alive false working_dir HOMEBREW_PREFIX log_path var/"log/fastnetmon.log" error_log_path var/"log/fastnetmon.log" end test do cp etc/"fastnetmon.conf", testpath inreplace testpath/"fastnetmon.conf", "/tmp/fastnetmon.dat", (testpath/"fastnetmon.dat").to_s inreplace testpath/"fastnetmon.conf", "/tmp/fastnetmon_ipv6.dat", (testpath/"fastnetmon_ipv6.dat").to_s fastnetmon_pid = fork do exec opt_sbin/"fastnetmon", "--configuration_file", testpath/"fastnetmon.conf", "--log_to_console", "--disable_pid_logic" end sleep 15 assert_path_exists testpath/"fastnetmon.dat" ipv4_stats_output = (testpath/"fastnetmon.dat").read assert_match("Incoming traffic", ipv4_stats_output) assert_path_exists testpath/"fastnetmon_ipv6.dat" ipv6_stats_output = (testpath/"fastnetmon_ipv6.dat").read assert_match("Incoming traffic", ipv6_stats_output) ensure Process.kill "SIGTERM", fastnetmon_pid end end upstream-fastnetmon/src/notify_about_attack.sh0000775000175000017500000000171415060514305020041 0ustar meme#!/bin/sh # # This script will get following arguments from FastNetMon: # # $1 IP of host which is under attack (incoming attack) or source of attack (outgoing attack) # $2 Attack direction: incoming or outgoing # $3 Attack bandwidth in packets per second # $4 Attack action: ban or unban # email_notify="please_fix_this_email@domain.com" # For ban action we will receive attack details to stdin # Please do not remove "cat" command because # FastNetMon will crash in this case as it expects read of data from script side # if [ "$4" = "ban" ]; then # This action receives multiple statistics about attack's performance and attack's sample to stdin cat | mail -s "FastNetMon Community: IP $1 blocked because $2 attack with power $3 pps" $email_notify; # Please add actions to run when we ban host exit 0 fi if [ "$4" = "unban" ]; then # No details provided to stdin here # Please add actions to run when we unban host exit 0 fi upstream-fastnetmon/src/abstract_subnet_counters.hpp0000664000175000017500000002351115060514305021266 0ustar meme#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)); } }; upstream-fastnetmon/src/fast_platform.h.template0000664000175000017500000000216515060514305020276 0ustar meme#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}"; }; upstream-fastnetmon/src/pcap_reader.cpp0000664000175000017500000001142415060514305016421 0ustar meme#include #include #include #include #include #include #include #include #include #include #include #include "fastnetmon_pcap_format.h" #include #include #include #include "netflow_plugin/netflow_collector.h" #include "sflow_plugin/sflow_collector.h" #include "sflow_plugin/sflow.h" #include "sflow_plugin/sflow_data.h" #include "fast_library.h" #include "fastnetmon_types.h" #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 "../simple_packet_parser_ng.hpp" // Fake config std::map configuration_map; std::string log_file_path = "/tmp/fastnetmon_pcap_reader.log"; log4cpp::Category& logger = log4cpp::Category::getRoot(); uint64_t total_unparsed_packets = 0; uint64_t dns_amplification_packets = 0; uint64_t ntp_amplification_packets = 0; uint64_t ssdp_amplification_packets = 0; uint64_t raw_parsed_packets = 0; uint64_t raw_unparsed_packets = 0; /* It's prototype for moc testing of FastNetMon, it's very useful for netflow or direct packet * parsers debug */ void init_logging() { log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("%d [%p] %m%n"); log4cpp::Appender* appender = new log4cpp::FileAppender("default", log_file_path); appender->setLayout(layout); logger.setPriority(log4cpp::Priority::INFO); logger.addAppender(appender); logger.info("Logger initialized"); } void pcap_parse_packet(const char* flow_type, char* buffer, uint32_t len); void my_fastnetmon_packet_handler(simple_packet_t& current_packet) { std::cout << print_simple_packet(current_packet); } extern process_packet_pointer netflow_process_func_ptr; extern process_packet_pointer sflow_process_func_ptr; char* flow_type = NULL; void pcap_parse_packet(char* buffer, uint32_t len, uint32_t snap_len) { struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = len; packet_header.caplen = snap_len; fastnetmon_parse_pkt((u_char*)buffer, &packet_header, 4, 1, 0); // char print_buffer[512]; // fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)buffer, &packet_header); // logger.info("%s", print_buffer); char* payload_ptr = packet_header.extended_hdr.parsed_pkt.offset.payload_offset + buffer; if (packet_header.len < packet_header.extended_hdr.parsed_pkt.offset.payload_offset) { printf("Something goes wrong! Offset %u is bigger than total packet length %u\n", packet_header.extended_hdr.parsed_pkt.offset.payload_offset, packet_header.len); return; } unsigned int payload_length = packet_header.len - packet_header.extended_hdr.parsed_pkt.offset.payload_offset; if (strcmp(flow_type, "netflow") == 0) { netflow_process_func_ptr = my_fastnetmon_packet_handler; std::string fake_peer_ip = "10.0.1.2"; process_netflow_packet((u_int8_t*)payload_ptr, payload_length, fake_peer_ip); } else if (strcmp(flow_type, "sflow") == 0) { sflow_process_func_ptr = my_fastnetmon_packet_handler; SFSample sample; memset(&sample, 0, sizeof(sample)); sample.rawSample = (uint8_t*)payload_ptr; sample.rawSampleLen = payload_length; sample.sourceIP.type = SFLADDRESSTYPE_IP_V4; read_sflow_datagram(&sample); } else if (strcmp(flow_type, "raw") == 0) { simple_packet_t packet; // TODO: add support for caplen here! auto result = parse_raw_packet_to_simple_packet_full_ng((u_char*)buffer, len, len, packet, false, false); if (result == network_data_stuctures::parser_code_t::success) { std::cout << "High level parser: " << print_simple_packet(packet) << std::endl; } else { printf("High level parser failed\n"); } } else { printf("We do not support this flow type: %s\n", flow_type); } } int main(int argc, char** argv) { init_logging(); if (argc != 3) { printf("Please provide flow type: sflow, netflow, raw and path to pcap dump\n"); exit(1); } flow_type = argv[1]; printf("We will process file: %s as %s dump\n", argv[2], argv[1]); pcap_reader(argv[2], pcap_parse_packet); if (strcmp(flow_type, "raw") == 0) { printf("Parsed packets: %llu\n", raw_parsed_packets); printf("Unparsed packets: %llu\n", raw_unparsed_packets); printf("Total packets: %llu\n", raw_parsed_packets + raw_unparsed_packets); } } upstream-fastnetmon/src/fastnetmon_client.cpp0000664000175000017500000000552215060514305017672 0ustar meme#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(); } upstream-fastnetmon/src/fastnetmon_logic.cpp0000664000175000017500000043112615060546662017527 0ustar meme#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; } } upstream-fastnetmon/src/grep.sh0000755000175000017500000000027414230006537014744 0ustar meme#!/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 upstream-fastnetmon/src/CMakeLists.txt0000664000175000017500000015000015060546662016215 0ustar meme# 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() upstream-fastnetmon/src/fastnetmon_logic.hpp0000664000175000017500000001401215060514305017510 0ustar meme#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); upstream-fastnetmon/src/fastnetmon_logrotate0000664000175000017500000000020215060514305017621 0ustar meme/var/log/fastnetmon.log { rotate 31 daily size 10M missingok copytruncate notifempty delaycompress } upstream-fastnetmon/src/juniper_plugin/0000755000175000017500000000000015060514305016476 5ustar memeupstream-fastnetmon/src/juniper_plugin/README.md0000664000175000017500000000263315060514305017763 0ustar memeJuniper 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 upstream-fastnetmon/src/juniper_plugin/fastnetmon_juniper.php0000664000175000017500000001024715060514305023127 0ustar meme#!/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 ******************************/ define( "_VER", '1.0' ); $date = date("Y-m-d H:i:s", time()); // 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 $cfg['hostname'] = "10.0.0.1"; // Juniper IP $cfg['port'] = 880; //NETCONF Port $cfg['username'] = "user"; //user $cfg['password'] = "password"; //pass /* PARAMS( $argv[1] = STRING (IP) $argv[2] = STRING (ATTACK DIRECTION) $argv[3] = STRING (PPS) $argv[4] = STRING (ACTION = BAN OR UNBAN) ) */ $IP_ATTACK = $argv[ 1 ]; $DIRECTION_ATTACK = $argv[ 2 ]; $POWER_ATTACK = $argv[ 3 ]; $ACTION_ATTACK = $argv[ 4 ]; if ( $argc <= 4 ) { $msg .= "Juniper API Integration for FastNetMon - Ver: " . _VER . "\n"; $msg .= "missing arguments"; $msg .= "php fastnetmon_juniper.php [IP] [data_direction] [pps_as_string] [action] \n"; echo $msg; exit( 1 ); } //NOTE help if ( $argv[ 1 ] == "help" ) { $msg = "Juniper API Integration for FastNetMon - Ver: " . _VER; echo $msg; exit( 1 ); } require_once "netconf/netconf/Device.php"; $conn = new Device($cfg); switch($ACTION_ATTACK){ case 'ban': try{ $desc = 'FastNetMon Community: IP '. $IP_ATTACK .' unblocked because '. $DIRECTION_ATTACK .' attack with power '. $POWER_ATTACK .' pps | at '.$fecha_now; $conn->connect(); //Try conect or catch NetconfException (Wrong username, Timeout, Device not found, etc) $locked = $conn->lock_config(); //Equivalent of "configure exclusive" on Juniper CLI 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(); //Unlock the CLI $conn->close(); //Close the connection _log($desc); } catch(NetconfException $e){ $msg = "Couldn't connect to " . $cfg['hostname'] . '\nLOG: '.$e; _log( $msg ); echo $msg; exit( 1 ); } break; case 'unban': try{ $desc = 'FastNetMon Community: IP '. $IP_ATTACK .' remove from blacklist.'; $conn->connect(); //Try conect or catch NetconfException (Wrong username, Timeout, Device not found, etc) $locked = $conn->lock_config(); //Equivalent of "configure exclusive" on Juniper CLI if($locked){ $conn->load_set_configuration("delete routing-options static route {$IP_ATTACK}/32"); $conn->commit(); } $conn->unlock(); //Unlock the CLI $conn->close(); //Close the connection _log($desc); } catch(NetconfException $e){ $msg = "Couldn't connect to " . $cfg['hostname'] . '\nLOG: '.$e; _log( $msg ); echo $msg; exit( 1 ); } break; default: $msg = "Juniper API Integration for FastNetMon - Ver: " . _VER; echo $msg; exit( 1 ); break; } /** * [_log Write a log file] * @param [type] $msg [text to log] * @return [type] */ function _log( $msg ) { $FILE_LOG_TMP = "/tmp/fastnetmon_api_juniper.log"; if ( !file_exists( $FILE_LOG_TMP ) ) exec( "echo `date` \"- [FASTNETMON] - " . $msg . " \" > " . $FILE_LOG_TMP ); else exec( "echo `date` \"- [FASTNETMON] - " . $msg . " \" >> " . $FILE_LOG_TMP ); } ?> upstream-fastnetmon/src/juniper_plugin/netconf/0000755000175000017500000000000014230006537020133 5ustar memeupstream-fastnetmon/src/juniper_plugin/notify_about_attack.sh0000755000175000017500000000063214230006537023070 0ustar meme#!/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 upstream-fastnetmon/src/ip_lookup_tree_with_payload.hpp0000664000175000017500000001473415060514305021754 0ustar meme#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; }; upstream-fastnetmon/src/subnet_counter.hpp0000664000175000017500000001324115060514305017217 0ustar meme#include #include // This class keeps all our counters for specific traffic type class traffic_counter_element_t { public: uint64_t in_bytes = 0; uint64_t out_bytes = 0; uint64_t in_packets = 0; uint64_t out_packets = 0; void zeroify() { in_bytes = 0; out_bytes = 0; in_packets = 0; out_packets = 0; } // Returns zero when all counters are zero bool is_zero() const { return in_bytes == 0 && out_bytes == 0 && in_packets == 0 && out_packets == 0; } // This function compares value in object with passed value and updates our object value to it if it passed value exceeds value we have in place void update_if_larger(const traffic_counter_element_t& another_value) { if (another_value.in_bytes > this->in_bytes) { this->in_bytes = another_value.in_bytes; } if (another_value.out_bytes > this->out_bytes) { this->out_bytes = another_value.out_bytes; } if (another_value.in_packets > this->in_packets) { this->in_packets = another_value.in_packets; } if (another_value.out_packets > this->out_packets) { this->out_packets = another_value.out_packets; } } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(in_bytes); ar& BOOST_SERIALIZATION_NVP(out_bytes); ar& BOOST_SERIALIZATION_NVP(in_packets); ar& BOOST_SERIALIZATION_NVP(out_packets); } // Calculates speed for all counters from input data counter void calculate_speed(const traffic_counter_element_t& traffic_counter, double speed_calc_period) { this->in_packets = uint64_t((double)traffic_counter.in_packets / speed_calc_period); this->out_packets = uint64_t((double)traffic_counter.out_packets / speed_calc_period); this->in_bytes = uint64_t((double)traffic_counter.in_bytes / speed_calc_period); this->out_bytes = uint64_t((double)traffic_counter.out_bytes / speed_calc_period); } // Calculates exponential moving average speed from instant speed void calulate_exponential_moving_average_speed(const traffic_counter_element_t& new_speed_element, double exp_value) { // Bytes counters this->in_bytes = uint64_t(new_speed_element.in_bytes + exp_value * ((double)this->in_bytes - (double)new_speed_element.in_bytes)); this->out_bytes = uint64_t(new_speed_element.out_bytes + exp_value * ((double)this->out_bytes - (double)new_speed_element.out_bytes)); // Packet counters this->in_packets = uint64_t(new_speed_element.in_packets + exp_value * ((double)this->in_packets - (double)new_speed_element.in_packets)); this->out_packets = uint64_t(new_speed_element.out_packets + exp_value * ((double)this->out_packets - (double)new_speed_element.out_packets)); } }; // Main data structure for storing traffic and speed data for all our IPs class subnet_counter_t { public: // We use inaccurate time source for it because we do not care about precise time in this case time_t last_update_time = 0; traffic_counter_element_t total; traffic_counter_element_t tcp; traffic_counter_element_t udp; traffic_counter_element_t icmp; traffic_counter_element_t fragmented; traffic_counter_element_t tcp_syn; // Total number of dropped traffic traffic_counter_element_t dropped; // Updates specific value if any of fields from another_value exceed values in our object void update_if_larger(const subnet_counter_t& another_value) { this->total.update_if_larger(another_value.total); this->tcp.update_if_larger(another_value.tcp); this->udp.update_if_larger(another_value.udp); this->icmp.update_if_larger(another_value.icmp); this->fragmented.update_if_larger(another_value.fragmented); this->tcp_syn.update_if_larger(another_value.tcp_syn); this->dropped.update_if_larger(another_value.dropped); if (in_flows < another_value.in_flows) { this->in_flows = another_value.in_flows; } if (out_flows < another_value.out_flows) { this->out_flows = another_value.out_flows; } } uint64_t in_flows = 0; uint64_t out_flows = 0; // Is total counters fields are zero? We are not handling per protocol counters here because we assume they should // be counted twice // Once: in total counter (in_bytes) and secondly in per protocol counter (for example: udp_in_bytes) bool is_zero() const { return total.in_bytes == 0 && total.out_bytes == 0 && total.in_packets == 0 && total.out_packets == 0 && in_flows == 0 && out_flows == 0; } // Fill all counters by zeros void zeroify() { total.zeroify(); dropped.zeroify(); tcp.zeroify(); udp.zeroify(); icmp.zeroify(); fragmented.zeroify(); tcp_syn.zeroify(); in_flows = 0; out_flows = 0; } template void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) { ar& BOOST_SERIALIZATION_NVP(total); ar& BOOST_SERIALIZATION_NVP(dropped); 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(in_flows); ar& BOOST_SERIALIZATION_NVP(out_flows); } }; upstream-fastnetmon/src/api.cpp0000664000175000017500000004604115060514305014730 0ustar meme#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; } upstream-fastnetmon/src/Dockerfile0000664000175000017500000000107715060514305015445 0ustar memeFROM 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"] upstream-fastnetmon/src/networks_whitelist0000644000175000017500000000000014230006537017326 0ustar memeupstream-fastnetmon/src/simple_packet_capnp/0000755000175000017500000000000015060514305017445 5ustar memeupstream-fastnetmon/src/simple_packet_capnp/simple_packet.capnp0000664000175000017500000000227315060514305023316 0ustar meme@0xa8a892437a5fd28f; # To regenerate, please run: LD_LIBRARY_PATH=/opt/gcc820/lib64 PATH=$PATH:/opt/capnproto_0_8_0/bin /opt/capnproto_0_8_0/bin/capnp compile --output c++:/home/odintsov/repos/fastnetmon_github/src/simple_packet_capnp --src-prefix=/home/odintsov/repos/fastnetmon_github/src/simple_packet_capnp /home/odintsov/repos/fastnetmon_github/src/simple_packet_capnp/simple_packet.capnp struct SimplePacketType { source @0 :UInt8; sampleRatio @1 :UInt32; srcIp @2 :UInt32; dstIp @3 :UInt32; srcIpv6 @4 :Data; dstIpv6 @5 :Data; srcMac @26 :Data; dstMac @27 :Data; srcAsn @6 :UInt32; dstAsn @7 :UInt32; inputInterface @8 :UInt32; outputInterface @9 :UInt32; ipProtocolVersion @10 :UInt8; ttl @11 :UInt8; sourcePort @12 :UInt16; destinationPort @13 :UInt16; protocol @14 :UInt32; length @15 :UInt64; ipLength @28 :UInt64; numberOfPackets @16 :UInt64; flags @17 :UInt8; ipFragmented @18 :Bool; ipDontFragment @19 :Bool; tsSec @20 :Int64; tsMsec @21 :Int64; packetPayloadLength @22 :Int32; packetPayloadFullLength @23 :UInt32; packetDirection @24 :UInt8; agentIpAddress @25 :UInt32; } upstream-fastnetmon/src/scripts/0000755000175000017500000000000015060514305015133 5ustar memeupstream-fastnetmon/src/scripts/build_any_package.pl0000775000175000017500000003606615060514305021131 0ustar meme#!/usr/bin/perl use strict; use warnings; my $error_message = "Please specify package type, original binary file name, version, distro name and version: rpm fastnetmon-binary-git-0cfdfd5e2062ad94de24f2f383576ea48e6f3a07-debian-6.0.10-x86_64 2.0.1 centos 8"; unless (scalar @ARGV == 5) { die "$error_message\n"; } my $package_type = $ARGV[0]; my $archive_name = $ARGV[1]; my $package_version = $ARGV[2]; my $distro_name = $ARGV[3]; my $distro_version = $ARGV[4]; unless ($package_type && $archive_name && $package_version && $distro_name && $distro_version) { die "$error_message\n"; } # Gzip does not compress well, let's use xz instead # By default it uses compression level 6 my $dpkg_deb_options = '-Zxz -z6'; # It can be: x86_64 or aarch64 my $machine_architecture = `uname -m`; chomp $machine_architecture; # For number of places we need Debian specific name for particular architectures my $debian_architecture_name = 'amd64'; if ($machine_architecture eq 'aarch64') { $debian_architecture_name = 'arm64'; } elsif ($machine_architecture eq 'x86_64') { $debian_architecture_name = 'amd64'; } if ($package_type eq 'rpm') { build_rpm_package(); } elsif ($package_type eq 'deb') { build_deb_package(); } sub build_rpm_package { print "Install packages for crafting rpm packages\n"; my $packages_install = system("yum install -y rpmdevtools yum-utils"); if ($packages_install != 0) { die "Cannot install build packages\n"; } mkdir '/root/rpmbuild' or die "Cannot create rpmbuild folder";; mkdir '/root/rpmbuild/SOURCES' or die "Cannot create source folder"; my $systemd_init_script = <<'DOC'; [Unit] Description=FastNetMon - DoS/DDoS analyzer with sFlow/Netflow/mirror support After=network.target remote-fs.target [Service] Type=simple ExecStart=/opt/fastnetmon-community/app/bin/fastnetmon LimitNOFILE=65535 # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target DOC my $rpm_sources_path = '/root/rpmbuild/SOURCES'; # Copy bundle to build tree my $copy_res = system("cp $archive_name $rpm_sources_path/archive.tar.gz"); if ($copy_res != 0) { die "Cannot copy file $archive_name to $rpm_sources_path/archive.tar.gz\n"; } if (defined($ENV{'CIRCLECI'})) { my $conf_path = $ENV{'CIRCLE_WORKING_DIRECTORY'} . '/src/fastnetmon.conf'; my $conf_copy_res = system("cp $conf_path $rpm_sources_path/fastnetmon.conf"); if ($conf_copy_res != 0) { die "Cannot copy fastnetmon.conf from $conf_path to $rpm_sources_path/fastnetmon.conf\n"; } } else { my $wget_res = system("wget --no-check-certificate https://raw.githubusercontent.com/pavel-odintsov/fastnetmon/master/src/fastnetmon.conf -O$rpm_sources_path/fastnetmon.conf"); if ($wget_res != 0) { die "Cannot download fastnetmon.conf\n"; } } put_text_to_file("$rpm_sources_path/systemd_init", $systemd_init_script); # Create files list from archive # ./libname_1.2.3/ my @files_list = `tar -tf /root/rpmbuild/SOURCES/archive.tar.gz`; chomp @files_list; # Replace path @files_list = map { s#^\.#/opt#; $_ } @files_list; # Filter out folders @files_list = grep { ! m#/$# } @files_list; if (scalar @files_list == 0) { die "Files must not be empty\n"; } my $spec_file_header = <<'DOC'; # # Pre/post params: https://fedoraproject.org/wiki/Packaging:ScriptletSnippets # %global fastnetmon_attackdir %{_localstatedir}/log/fastnetmon_attacks %global fastnetmon_user root %global fastnetmon_group %{fastnetmon_user} %global fastnetmon_config_path %{_sysconfdir}/fastnetmon.conf Name: fastnetmon DOC # We do need variable interpolation here my $spec_file_version = </dev/null 2>&1 #fi %preun %systemd_preun fastnetmon.service %postun %systemd_postun_with_restart fastnetmon.service %files /usr/bin/fastnetmon /usr/bin/fastnetmon_client /usr/bin/fastnetmon_api_client {files_list} %{_sysconfdir}/systemd/system %config(noreplace) %{_sysconfdir}/fastnetmon.conf %attr(700,%{fastnetmon_user},%{fastnetmon_group}) %dir %{fastnetmon_attackdir} %changelog * Mon Mar 23 2022 Pavel Odintsov - 1.2.1-1 - First RPM package release DOC my $selected_spec_file = $systemd_spec_file; # Add full list of files into RPM spec my $joined_file_list = join "\n", @files_list; $selected_spec_file =~ s/\{files_list\}/$joined_file_list/; put_text_to_file("generated_spec_file.spec", $selected_spec_file); my $rpmbuild_res = system("rpmbuild -bb generated_spec_file.spec"); if ($rpmbuild_res != 0) { die "Rpmbuild failed with code $rpmbuild_res\n"; } mkdir "/tmp/result_data" or die "Cannot create result_data folder"; my $copy_rpm_res = system("cp /root/rpmbuild/RPMS/$machine_architecture/* /tmp/result_data"); if ($copy_rpm_res != 0) { die "Cannot copy result rpm\n"; } print "Result RPM:\n"; print `ls -la /tmp/result_data`; } sub build_deb_package { print "We will build deb from $archive_name\n"; my $fastnetmon_systemd_unit = <<'DOC'; [Unit] Description=FastNetMon - DoS/DDoS analyzer with sFlow/Netflow/mirror support After=network.target remote-fs.target [Service] Type=simple ExecStart=/opt/fastnetmon-community/app/bin/fastnetmon LimitNOFILE=65535 # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target DOC my $fastnetmon_control_file = < Section: misc Priority: optional Architecture: $debian_architecture_name Version: $package_version Description: Very fast DDoS analyzer with sFlow/Netflow/IPFIX and mirror support FastNetMon - A high performance DoS/DDoS attack sensor. DOC my $fastnetmon_prerm_hook = <", $path or die "Can't open $! for writing\n"; print {$fl} $text; close $fl; } upstream-fastnetmon/src/scripts/fastnetmon_build.pl0000775000175000017500000003136315060514305021040 0ustar meme#!/usr/bin/perl use strict; use warnings; use File::Basename; # Use path to our libraries folder relvant to path where we keep script itself use FindBin; use lib "$FindBin::Bin/perllib"; my $fastnetmon_install_folder = '/opt/fastnetmon-community'; my $library_install_folder = "$fastnetmon_install_folder/libraries"; my $gcc_compiler_path = "$library_install_folder/gcc_12_1_0"; my $default_c_compiler_path = "$gcc_compiler_path/bin/gcc"; my $default_cpp_compiler_path = "$gcc_compiler_path/bin/g++"; my $cmake_path = "$library_install_folder/cmake_3_23_4/bin/cmake"; my $os_type = ''; my $distro_type = ''; my $distro_version = ''; my $distro_architecture = ''; my $install_log_path = "/tmp/fastnetmon_install_$$.log"; if (defined($ENV{'CI'}) && $ENV{'CI'}) { $install_log_path = "/tmp/fastnetmon_install.log"; } my $fastnetmon_git_path = 'https://github.com/pavel-odintsov/fastnetmon.git'; my $temp_folder_for_building_project = `mktemp -d /tmp/fastnetmon.build.dir.XXXXXXXXXX`; chomp $temp_folder_for_building_project; unless ($temp_folder_for_building_project && -e $temp_folder_for_building_project) { die("Can't create temp folder in /tmp for building project: $temp_folder_for_building_project"); } my $start_time = time(); my $fastnetmon_code_dir = "$temp_folder_for_building_project/fastnetmon/src"; # Used for VyOS and different appliances based on rpm/deb my $appliance_name = ''; my $cpus_number = 1; # We could pass options to make with this variable my $make_options = ''; main(); sub get_logical_cpus_number { if ($os_type eq 'linux') { my @cpuinfo = `cat /proc/cpuinfo`; chomp @cpuinfo; my $cpus_number = scalar grep {/processor/} @cpuinfo; return $cpus_number; } elsif ($os_type eq 'macosx' or $os_type eq 'freebsd') { my $cpus_number = `sysctl -n hw.ncpu`; chomp $cpus_number; } } sub install_additional_repositories { if ($distro_type eq 'centos') { if ($distro_version == 7) { print "Install EPEL repository for your system\n"; yum('https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm'); } elsif ($distro_version == 8) { print "Install EPEL repository for your system\n"; yum('https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm'); # Part of devel libraries was moved here https://github.com/pavel-odintsov/fastnetmon/issues/801 print "Enable PowerTools repo\n"; yum('dnf-plugins-core'); system("dnf config-manager --set-enabled powertools"); } elsif ($distro_version == 9) { print "Install EPEL repository for your system\n"; system("dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm"); print "Install CodeReady Linux Builder repository\n"; yum('dnf-plugins-core'); system("dnf config-manager --set-enabled crb"); } } } ### Functions start here sub main { # Open log file, we need to append it to keep logs for CI in single file open my $global_log, ">>", $install_log_path or warn "Cannot open log file: $! $install_log_path"; print {$global_log} "Install started"; detect_distribution(); # Set environment variables to collect more information about installation failures $cpus_number = get_logical_cpus_number(); if (defined($ENV{'CIRCLECI'}) && $ENV{'CIRCLECI'}) { my $circle_ci_cpu_number = 4; # We use machine with X CPUs, let's set explicitly X threads, get_logical_cpus_number returns 36 which is not real number of CPU cores $make_options = "-j $circle_ci_cpu_number"; print "Will use $circle_ci_cpu_number CPUs for build process\n"; } else { if ($cpus_number > 1) { print "Will use $cpus_number CPUs for build process\n"; $make_options = "-j $cpus_number"; } } # CentOS base repository is very very poor and we need EPEL for some dependencies install_additional_repositories(); # Refresh information about packages init_package_manager(); unless (-e $library_install_folder) { exec_command("mkdir -p $library_install_folder"); } install_fastnetmon(); my $install_time = time() - $start_time; my $pretty_install_time_in_minutes = sprintf("%.2f", $install_time / 60); print "We have built project in $pretty_install_time_in_minutes minutes\n"; } sub exec_command { my $command = shift; open my $fl, ">>", $install_log_path; print {$fl} "We are calling command: $command\n\n"; my $output = `$command >> $install_log_path 2>&1`; print {$fl} "Command finished with code $?\n\n"; if ($? == 0) { return 1; } else { return ''; } } sub init_package_manager { print "Update package manager cache\n"; if ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { exec_command("apt-get update"); } } # Detect operating system of this machine sub detect_distribution { # We use following global variables here: # $os_type, $distro_type, $distro_version, $appliance_name my $uname_s_output = `uname -s`; chomp $uname_s_output; # uname -a output examples: # FreeBSD 10.1-STABLE FreeBSD 10.1-STABLE #0 r278618: Thu Feb 12 13:55:09 UTC 2015 root@:/usr/obj/usr/src/sys/KERNELWITHNETMAP amd64 # Darwin MacBook-Pro-Pavel.local 14.5.0 Darwin Kernel Version 14.5.0: Wed Jul 29 02:26:53 PDT 2015; root:xnu-2782.40.9~1/RELEASE_X86_64 x86_64 # Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:43:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux if ($uname_s_output =~ /FreeBSD/) { $os_type = 'freebsd'; } elsif ($uname_s_output =~ /Darwin/) { $os_type = 'macosx'; } elsif ($uname_s_output =~ /Linux/) { $os_type = 'linux'; } else { warn "Can't detect platform operating system\n"; } if ($os_type eq 'linux') { # x86_64 or i686 $distro_architecture = `uname -m`; chomp $distro_architecture; if (-e "/etc/debian_version") { # Well, on this step it could be Ubuntu or Debian # We need check issue for more details my @issue = `cat /etc/issue`; chomp @issue; my $issue_first_line = $issue[0]; # Possible /etc/issue contents: # Debian GNU/Linux 8 \n \l # Ubuntu 14.04.2 LTS \n \l # Welcome to VyOS - \n \l my $is_proxmox = ''; # Really hard to detect https://github.com/proxmox/pve-manager/blob/master/bin/pvebanner for my $issue_line (@issue) { if ($issue_line =~ m/Welcome to the Proxmox Virtual Environment/) { $is_proxmox = 1; $appliance_name = 'proxmox'; last; } } if ($issue_first_line =~ m/Debian/ or $is_proxmox) { $distro_type = 'debian'; $distro_version = `cat /etc/debian_version`; chomp $distro_version; # Debian 6 example: 6.0.10 # We will try transform it to decimal number if ($distro_version =~ /^(\d+\.\d+)\.\d+$/) { $distro_version = $1; } } elsif ($issue_first_line =~ m/Ubuntu Jammy Jellyfish/) { # It's pre release Ubuntu 22.04 $distro_type = 'ubuntu'; $distro_version = "22.04"; } elsif ($issue_first_line =~ m/Ubuntu (\d+(?:\.\d+)?)/) { $distro_type = 'ubuntu'; $distro_version = $1; } elsif ($issue_first_line =~ m/VyOS/) { # Yes, VyOS is a Debian $distro_type = 'debian'; $appliance_name = 'vyos'; my $vyos_distro_version = `cat /etc/debian_version`; chomp $vyos_distro_version; # VyOS have strange version and we should fix it if ($vyos_distro_version =~ /^(\d+)\.\d+\.\d+$/) { $distro_version = $1; } } } if (-e "/etc/redhat-release") { $distro_type = 'centos'; my $distro_version_raw = `cat /etc/redhat-release`; chomp $distro_version_raw; # CentOS 6: # CentOS release 6.6 (Final) # CentOS 7: # CentOS Linux release 7.0.1406 (Core) # Fedora release 21 (Twenty One) if ($distro_version_raw =~ /(\d+)/) { $distro_version = $1; } } if (-e "/etc/gentoo-release") { $distro_type = 'gentoo'; my $distro_version_raw = `cat /etc/gentoo-release`; chomp $distro_version_raw; } unless ($distro_type) { die("This distro is unsupported"); } print "We detected your OS as $distro_type Linux $distro_version\n"; } elsif ($os_type eq 'macosx') { my $mac_os_versions_raw = `sw_vers -productVersion`; chomp $mac_os_versions_raw; if ($mac_os_versions_raw =~ /(\d+\.\d+)/) { $distro_version = $1; } print "We detected your OS as Mac OS X $distro_version\n"; } elsif ($os_type eq 'freebsd') { my $freebsd_os_version_raw = `uname -r`; chomp $freebsd_os_version_raw; if ($freebsd_os_version_raw =~ /^(\d+)\.?/) { $distro_version = $1; } print "We detected your OS as FreeBSD $distro_version\n"; } } sub apt_get { my @packages_list = @_; # We install one package per apt-get call because installing multiple packages in one time could fail of one package is broken for my $package (@packages_list) { exec_command("DEBIAN_FRONTEND=noninteractive apt-get install -y --force-yes $package"); if ($? != 0) { print "Package '$package' install failed with code $?\n" } } } sub yum { my @packages_list = @_; for my $package (@packages_list) { exec_command("yum install -y $package"); if ($? != 0) { print "Package '$package' install failed with code $?\n"; } } } sub install_fastnetmon_dependencies { print "Install FastNetMon dependency list\n"; if ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { my @fastnetmon_deps = ("libncurses5-dev", "libpcap-dev"); apt_get(@fastnetmon_deps); } elsif ($distro_type eq 'centos') { my @fastnetmon_deps = ('ncurses-devel', 'libpcap-devel'); yum(@fastnetmon_deps); } } sub install_fastnetmon { install_fastnetmon_dependencies(); print "Clone FastNetMon repo\n"; chdir $temp_folder_for_building_project; # Pull code exec_command("git clone $fastnetmon_git_path"); if ($? != 0) { die "Can't clone source code"; } exec_command("mkdir -p $fastnetmon_code_dir/build"); chdir "$fastnetmon_code_dir/build"; # We enable Kafka support only for our releases # By default it's disabled as we have no cppkafka for many distributions # Same with Clickhouse my $cmake_params = "-DDO_NOT_USE_SYSTEM_LIBRARIES_FOR_BUILD=ON -DKAFKA_SUPPORT=ON -DBUILD_TESTS=ON -DCLICKHOUSE_SUPPORT=ON "; # Test that atomics build works as expected # $cmake_params .= " -DUSE_NEW_ATOMIC_BUILTINS=ON"; # We need to specify path to libraries of gcc. Otherwise it will not work well my $ld_library_path = "LD_LIBRARY_PATH=$gcc_compiler_path/lib64"; print "Run cmake to generate make file\n"; my $cmake_result = system("$ld_library_path CC=$default_c_compiler_path CXX=$default_cpp_compiler_path $cmake_path .. $cmake_params"); if ($cmake_result != 0) { die "cmake call failed\n"; } print "Run make to build FastNetMon\n"; my $make_result = system("$ld_library_path make $make_options"); if ($make_result != 0) { die "make call failed\n"; } my $fastnetmon_build_binary_path = "$fastnetmon_code_dir/build/fastnetmon"; mkdir $fastnetmon_install_folder; print "Install fastnetmon to directory $fastnetmon_install_folder\n"; system("mkdir -p $fastnetmon_install_folder/app/bin"); exec_command("cp $fastnetmon_build_binary_path $fastnetmon_install_folder/app/bin/fastnetmon"); exec_command("cp $fastnetmon_code_dir/build/fastnetmon_client $fastnetmon_install_folder/app/bin/fastnetmon_client"); exec_command("cp $fastnetmon_code_dir/build/fastnetmon_api_client $fastnetmon_install_folder/app/bin/fastnetmon_api_client"); my $fastnetmon_config_path = "/etc/fastnetmon.conf"; unless (-e $fastnetmon_config_path) { print "Create stub configuration file\n"; exec_command("cp $fastnetmon_code_dir/fastnetmon.conf $fastnetmon_config_path"); } } upstream-fastnetmon/src/scripts/install_fastnetmon_dependencies.pl0000775000175000017500000023734215060514305024122 0ustar meme#!/usr/bin/perl ### ### This tool builds all binary dependencies required for FastNetMon ### use strict; use warnings; use FindBin; use lib "$FindBin::Bin/perllib"; use Fastnetmon; use Getopt::Long; # # CentOS # sudo yum install perl perl-Archive-Tar # my $library_install_folder = '/opt/fastnetmon-community/libraries'; my $os_type = ''; my $distro_type = ''; my $distro_version = ''; my $distro_architecture = ''; my $appliance_name = ''; my $temp_folder_for_building_project = `mktemp -d /tmp/fastnetmon.build.dir.XXXXXXXXXX`; chomp $temp_folder_for_building_project; unless ($temp_folder_for_building_project && -e $temp_folder_for_building_project) { die "Can't create temp folder in /tmp for building project: $temp_folder_for_building_project\n"; } # Pass log path to module $Fastnetmon::install_log_path = "/tmp/fastnetmon_install_$$.log"; # We do not need default very safe permissions exec_command("chmod 755 $temp_folder_for_building_project"); my $start_time = time(); my $fastnetmon_code_dir = "$temp_folder_for_building_project/fastnetmon/src"; unless (-e $library_install_folder) { exec_command("mkdir -p $library_install_folder"); } main(); ### Functions start here sub main { my $machine_information = Fastnetmon::detect_distribution(); unless ($machine_information) { die "Could not collect machine information\n"; } $distro_version = $machine_information->{distro_version}; $distro_type = $machine_information->{distro_type}; $os_type = $machine_information->{os_type}; $distro_architecture = $machine_information->{distro_architecture}; $appliance_name = $machine_information->{appliance_name}; $Fastnetmon::library_install_folder = $library_install_folder; $Fastnetmon::temp_folder_for_building_project = $temp_folder_for_building_project; # Install build dependencies my $dependencies_install_start_time = time(); install_build_dependencies(); print "Installed dependencies in ", time() - $dependencies_install_start_time, " seconds\n"; # Init environment init_compiler(); # We do not use prefix "lib" in names as all of them are libs and it's meaning less # We use target folder names in this list for clarity # Versions may be in different formats and we do not use them yet my @required_packages = ( 'pcap_1_10_4', # 'gcc', # we build it separately as it requires excessive amount of time 'openssl_1_1_1q', 'cmake_3_23_4', 'boost_build_4_9_2', 'icu_65_1', 'boost_1_81_0', 'capnproto_0_8_0', 'hiredis_0_14', 'mongo_c_driver_1_23_0', # gRPC dependencies 're2_2022_12_01', 'abseil_2024_01_16', 'zlib_1_3_1', 'cares_1_18_1', 'protobuf_21_12', 'grpc_1_49_2', 'elfutils_0_186', 'bpf_1_0_1', 'rdkafka_1_7_0', 'cppkafka_0_3_1', 'clickhouse_2_3_0', 'gobgp_3_12_0', 'log4cpp_1_1_4', 'gtest_1_13_0' ); # Accept package name from command line argument if (scalar @ARGV > 0) { @required_packages = @ARGV; } # To guarantee that binary dependencies are not altered in storage side we store their hashes in repository my $binary_build_hashes = { 'gcc_12_1_0' => { 'ubuntu:24.04' => '8fb7feef267313461fd21c1f9c2d397f82fb93969cd5f0d4a6f3d09cb7154d9a97252d612197b76dd20a8396ee6c0af990337a0449fef60bee304a21ab45d888', 'ubuntu:aarch64:24.04' => '4416ba59762859550b854b5c0ff0af65cc3e9bd78bda631068504b2535f10dbc85a401588d1653556117937a0c4d82bea491eb771d7d97a704a90f92bff091f0', 'debian:10' => '2c18964400a6660eae4ee36369c50829fda4ad4ee049c29aa1fd925bf96c3f8eed3ecb619cc02c6f470d0170d56aee1c840a4ca58d8132ca7ae395759aa49fc7', 'debian:11' => '3ad28bf950a7be070f1de9b3184f1fe9f42405cdbc0f980ab97e13d571a5be1441963a43304d784c135a43278454149039bd2a6252035c7755d4ba5e0eb41480', 'debian:aarch64:11' => '639d1435e732d108700b386ea5d7de5932884c1c0da0a389fab77f40c9c98f36c5c95049cd602dda502f5d73c1773bb51ac86d3ee7001762e8df78ce1809cff0', 'debian:12' => '907bf0bb451c5575105695a98c3b9c61ff67ad607bcd6a133342dfddd80d8eac69c7af9f97d215a7d4469d4885e5d6914766c77db8def4efa92637ab2c12a515', 'debian:aarch64:12' => '4eb380e6460ee87d2a030b15460648111bbb40e610a4eaed86830c5e3b3aef0f20d4a8d7625f994e976f4fd0dd1b7759d98a9cb20e9ca6bd3256677180991660', 'ubuntu:16.04' => '433789f72e40cb8358ea564f322d6e6c117f6728e5e1f16310624ee2606a1d662dad750c90ac60404916aaad1bbf585968099fffca178d716007454e05c49237', 'ubuntu:18.04' => '7955ab75d491bd19002e0e6d540d7821f293c2f8acb06fdf2cb5778cdae8967c636a2b253ee01416ea1cb30dc11d363d4a91fb59999bf3fc8f2d0030feaaba4e', 'ubuntu:20.04' => '0b69672a4f1f505e48a4d3a624f0b54b2b36b28a482456e4edba9f8085bfb51340beac006bf12e3dc90021bed627bf7d974f2bbfa2309eab12a7a062594cb497', 'ubuntu:aarch64:20.04'=> '1bddcc9fd8ffca7cbbbd2c29f77549e7b478b8e2a282afac0fe6b234d13002135ad21555842a552f261896f1a2f656af0527f85295ab0cb60061f797e2edb1b4', 'ubuntu:22.04' => '23c01edfb5a640bd1108a112366ed7c5862b75bdd16cbe376a8c23db2d5eb5fded70e8750e9a372c0279c950f1d3adf4d53f5233371cd2acbd11def3010561df', 'ubuntu:aarch64:22.04'=> '5578f8ffc4fcdfa13d3642ebdb990a1820fdd88670a005e59e193348c915ee7391a8a9ddbaabaac85c2a937c21f0310a8e9d89ce9b60eed0fdfba466f798e452', 'centos:7' => 'f7bb6b23d338fa80e1a72912d001ef926cbeb1df26f53d4c75d5e20ffe107e146637cb583edd08d02d252fc1bb93b2af6b827bd953713a9f55de148fb10b55aa', 'centos:8' => 'a3fcd2331143579f4178e5142a6949ba248278c8cea7cc70c977ebade1bf2b3bcea7b8115e1fbec8981042e0242578be822113e63b3dea68ae4279a63d9afd01', 'centos:aarch64:8' => 'd26f340a206017c102bdb5cbb6e85076cae13e7c78f0e8161f7403078fa8524ffa651d22e47fb630a6944c3655355a8b5f8ad27aaba32c71b91c987ca342232d', 'centos:9' => '8ee999dd3783abf99e79be4ba9c717a713330db7c17d1228c3dcdaa71c784f512d17b91d463f8dda3281ab07ed409439186d3c84385c480ed80b73fc86f0183a', 'centos:aarch64:9' => 'b8d18c9b682c91e1cebb0cf3b1efb2281c6bcea067261e63cff2dee076d4842db3cb213b8ef3d986e03491ddf38e59d4b7ccab5dbe9f82bb7e3d4ef9ddaa772a', }, 'openssl_1_1_1q' => { 'ubuntu:24.04' => '4359cad3dee0b022cb1949b425767e90cd2da09f9d9809e8ba2b0cf8866dab8bf1d3f74536f4b82bb0c3d691f11c1f78b6e9b5eb1fcf08cd02e14390c2a10ba4', 'ubuntu:aarch64:24.04'=> '6a504201bf3190597a5c923a73ec7e7a33093f622ee107905d3725a85b2ec0c8eba70994db0ad23b46fcb055847f28526e3e92aa777b36eabc1288186d6a9e44', 'centos:7' => 'ab9dde43afc7b6bcc4399b6fbd746727e0ce72cf254e9b7f6abcc5c22b319ab82c051546decc6804e508654975089888c544258514e30dc18385ad1dd59d63fb', 'centos:8' => 'c4c1fe35008606bc65bff4c125fae83738c397fb14081d59cb1e83ad5b8a69b9f80b7c91318c52613f00a7cf5b7a64dd6d23d2956c2ce083fc4c7502e81714cb', 'centos:aarch64:8' => '0b19b08417e3643c5d5fb7aeb327ea3ac4ea608e5297dea1eb78be69d2add48ac2a6ec6cc38dc446a84a084de13055fad8482b78ec15fd27ae0867bd3e9b2fb4', 'centos:9' => '76d2be30ca3afdf3e603b7690a3e7a8bf8423d4b359d928ef45f7aa827dec6d12e47c1f995c945d419763820d566590945aadf7d3ba38344b8b5d184fcc9bffc', 'centos:aarch64:9' => 'ab23ef0ecf1abf7badff5ae759f264ca0bf1749813b6825fbb1875040de0559d1561a97c32abb21b546229d16d81bac186ac5c5738bc3a854f4b5873a77215f7', 'debian:10' => 'eac2b5a066386f7900b1e364b5347d87ab4a994a058ecfaf5682a9325fc72362b8532ddf974e092c08bebd9f4cc4b433e00c3ab564c532fa6ed1f30a6b354626', 'debian:11' => 'd1b1aeecbfb848a0f58316e46b380a9c15773646fe79b3d8dca81cb9ef2dce84ee15375a19df6b1146ca19e05b38d42512aed1c35a8d26e9db0ebe0733acdff9', 'debian:aarch64:11' => 'f701670a10f42dbf414c5ad6e36636eb5fe40610e82f5a908ca6a000daa9664055cbe6a75207626927b582beb663478e18311fb1ea32327e0c3e69284d0a2aab', 'debian:12' => '793055b1e9cb0eb63b3e00d9e31a0f10447ffdf4166e642cc82b4fd78fd658a2c315db3911eec22fae57e5f859230fe557bb541462ae0af8e5d158295342762f', 'debian:aarch64:12' => '23e538879279c2067d88eced2853b02aaf088ae7893bd267a39e46780108ca908b260d9952f9b7877f6fef1747c66f17b29546d2048de81e9433f1aa94f2b770', 'ubuntu:16.04' => 'e5af3f4008f715319cc2ca69e6b3d5f720322887de5f7758e4cbd7090e5282bb172d1e4b26ef88ca9a5212efd852658034ddac51ea86c4ca166c86e46e7d5809', 'ubuntu:18.04' => 'ec1dbceef7c3db5aca772f0ec313a9220ead22347957e8c24951b536477093d6561c3b6b2c9d1b876b30b52f793b5b3ab7dc8c4c9b6518f56d144e88cb4508e9', 'ubuntu:20.04' => 'cbcccd25343826ac62e36ee7e843fb701be3d4e3a18643166d163c1d8aaf6d1b932f48161cbbd218a761aca89f72fc8dbfca7b329aa1e39b4e556364041ba242', 'ubuntu:aarch64:20.04'=> 'fd759a8bdbe107ab396c97b0c2bfd59045e7f5aa5e996e5d13d518cc491122fb22a4fbe3e59167bcb99ebe8b2fc676bea92755a2e1d6f4b483b69cf9b72e0cdc', 'ubuntu:22.04' => '40c8edde5b5798865190775336139f7f5c617bbde8d9413ef32382c10097eb747f5574ee3c672aaccaf1703067307cf6e3f7eae1340c45f0f6f2ce0dc3c899c8', 'ubuntu:aarch64:22.04'=> '303c01b34d86609341dfe6a77c6c0d51192ba654d71a142e2a1ae42c8085073c489586c6fb743f20a18f4d1128001cc15eb2a5d98c6523cad1bfbaef0bed089c', }, 'cmake_3_23_4' => { 'ubuntu:24.04' => '2f302be8dcaa073aadff64f77fe6392316da923191684a09339992feb5795f7f72c915ed262e7bb859146854748d2d2967c0b7c69f980edbacdf76c43c13bed2', 'ubuntu:aarch64:24.04' => 'a8d8af073d6af91ea5a257cbebd6ad4f5fa2aa2f9cf2160272ca7e7463d4b42e6ddb2f3c63b9d8f42a54f057a55fbcd05d96f1cdc9fa0ccd07ceefc465747383', 'centos:7' => 'f19d35583461af4a8e90a2c6d3751c586eaae3d18dcf849f992af9add78cf190afe2c5e010ddb9f5913634539222ceb218c2c04861b71691c38f231b3f49f6c5', 'centos:8' => 'dbe18cd4555aa60783554dcd06d84edac69640d15ef3ec7b4e2ff29e58b643fa8a0bcc2b838d6ae3c52a45043382e40a51888eaf1b45b2de3788931affc9e1c7', 'centos:aarch64:8' => 'bfbed76e9dd3cd64860001f500c413700ef9f58da44ff742b277bc1948df4c92bf574a62ac5a498e32991ed56f8e52837d348fcc89eae5afe2a580c4a267caeb', 'centos:9' => 'ffcfb14f224b24b67ca68edcf36b24d8dc6ddce47dc597ccf4d13301ce7d87e79c9fec67197ce1ee57b9acd8bde58633418b8c1eff1a85300a6f7af033263d2c', 'centos:aarch64:9' => 'f34540fc160b197b89f9a814794d4abdf3b9607fa1af5a81f9bd9478a3ae460799f26f35e433cd650f8857232b0251d18db6152c7782fc1e7ba2008d272cf685', 'debian:10' => 'cab3412debee6f864551e94f322d453daca292e092eb67a5b7f1cd0025d1997cfa60302dccc52f96be09127aee493ab446004c1e578e1725b4205f8812abd9ea', 'debian:11' => '9aac32d98835c485d9a7a82fd4269b8c5178695dd0ba28ed338212ca6c45a26bff9a0c511c111a45c286733d5cdf387bcc3fb1d00340c179db6676571e173656', 'debian:aarch64:11' => 'd5ce7b39cc0cf287a0e006928ff8e9da094664d6df5848cbc8b437284bfd916c9a9ce73aab115da3850a7a207cae1b8a4fada3a0bc97ab2e57900b6baf9594d3', 'debian:12' => '9f08c5776349b9491821669d3e480c5bbd072410f4b8419c0d12ffbf52b254bddb96ee6e89f02e547efcf6f811025dbbbc476e2506f3a30c34730d72ad1de656', 'debian:aarch64:12' => '40731965cdb6d3287a88dd0fe5a380ecdab92579d2e1ecc2bb75d1eb95ae0102822e08f0a5b1db0024dfc3651c66c30b332f7c4291cf80bab875a34f59af5c23', 'ubuntu:16.04' => '0b89bae5f0ed6104235c7fd77c22daee42ad15b8a7ce08e94c2f6bebdb342e6e5672c2678d15840a778fd43c7c51fdc83f53a70b436a79c2325892767d2067a1', 'ubuntu:18.04' => '1d0c06bea58cba2d03dbc4c9b17e12c07d5c41168473dee34bfcd7ab21169ab1082d9024458a62247ae7585cf98c86d8e64508c3eab9d9653dee1357481fc866', 'ubuntu:20.04' => 'f2bc63e9813ee7e233ca192ccd461776166992f3357500d30318dc9314db5e24f39b7e56f7a5d813c0ab3802bb48cd2c651e9c8bc68c3f6d6739b14a1412f6ac', 'ubuntu:aarch64:20.04'=> '367183c8f2eeb6b5d98694733cfe8033c3511765634dafa2f06e00e0460c74b82ee3045a7a59e1df5b0dd74999377156c260d01c5a498362942fb9ef5d89929c', 'ubuntu:22.04' => '8ee9c1ce4f82434bf18473a7910a649afd7132377a15c7ed12e3844d04b5d804e92be2cccb5b6c6cbe46459f8d42bc1ff09f4e325f7b5c1c2542e31552f0bd09', 'ubuntu:aarch64:22.04'=> '6c22838bcf91e31e54ed9a1b015019d84d2b527205dd07be970bd6190d3b9be3a54cee83a1a94138cb5abb264b36e0d1e397feb1fdaf35c7c2529f58162699a4', }, 'boost_build_4_9_2' => { 'ubuntu:24.04' => 'cfcbdee292749e2e5367aaf23b2ae088ebd1512ca325cd03375329076f0f61056212b3919a2ae2643c06d4f5543817eab5a03772557464a781b2c3ddc87fb7eb', 'ubuntu:aarch64:24.04' => 'cd81eb33fa82955171b71f9c8ae1fb44834ead1e0d394118bc5385b0cf05edec71d9026ea55dd593543e1002b9cf1668dfa684f3c369799d1568d2de28339236', 'centos:7' => 'd395a8e369d06e8c8ef231d2ffdaa9cacbc0da9dc3520d71cd413c343533af608370c7b9a93843facbd6d179270aabebc9dc4a56f0c1dea3fe4e2ffb503d7efd', 'centos:8' => '7e79ac11badf496a70af00f87afca2f4cab915b017f06733bd5ba4524d1083f22c5a89a46ee4bad97aaf2b5bdefd65eb92abe63d4857618ac8af1a068700ff18', 'centos:aarch64:8' => '8f25c3ae1cb8334fcc8f9db739374ce0dab760311309897eadcd567cab3d7695bc53d901189975a1d48bf8e5c040ea66331be040bb7599f0040f4b786d151f64', 'centos:9' => 'fb604dab4188dfa7d81483274fe30daa8ddc27bd8ea0ba37eaf7171db781f397750ab8a27edb160895307ee5e5c89f3b59478cb7f40e7e6113513c76965b6c21', 'centos:aarch64:9' => '4cf47707e4e542cab65f3482bf8ba76462d263c14369bb675f7bae9f9c70e880286138cd258de28de050359350f464a829bdf8e4ded5394a1bf3808be7df5cd6', 'debian:10' => '89c1a916456f85aa76578d5d85b2c0665155e3b7913fd79f2bb6309642dab54335b6febcf6395b2ab4312c8cc5b3480541d1da54137e83619f825a1be3be2e4e', 'debian:11' => 'f434ddf167a36c5ec5f4dd87c9913fb7463427e4cda212b319d245a8df7c0cb41ec0a7fb399292a7312af1c118de821cf0d87ac9dcd00eed2ea06f59e3415da2', 'debian:aarch64:11' => '8615b98a0e078fc978d05e7288272f63c06c2aa21c5ed8bb3b4a52c504bf0ccc839335ed2c216d97fef01a061fd41dbc35da4ad2253690968dcee36d9a843d1c', 'debian:12' => '283835e4cb70db05f205280334614391d932bea4dc71916192e80f7625b51aade7b939f4a683c310f49d7fbcd815318b261d8d34668bb3cc205015448dc973b3', 'debian:aarch64:12' => 'df6153bc3ac3677a1772af3af590ac421a0f4ab0999afe8480cf48a79c7a7bcb2f05b4e28f9c844dda707c03c7f2b54ec7d98abd46c62792026323cee0607d1a', 'ubuntu:16.04' => '0361e191dc9010bbe725eaccea802adad2fced77c4d0152fc224c3fd8dfc474dd537945830255d375796d8153ecfb985d6ee16b5c88370c24dbd28a5f71a8f56', 'ubuntu:18.04' => 'bc4287b1431776ae9d2c2c861451631a6744d7ae445630e96a2bb2d85a785935e261735c33a4a79a4874e6f00a7dd94252bc5e88ddce85b41f27fba038fea5a2', 'ubuntu:20.04' => '58ee3e5b8f6f58f1a774c7269c64a8dcb4f5013748fa11a2adab4e97b55614c867fcc37b536b6fcbc9c3eea678b356c26ae0e3a59284b06e5222b003c2636e16', 'ubuntu:aarch64:20.04'=> 'ff27410a164727aadc7d9f0816ecef2fb541d22d7b1bb75d055adc5acf4518b40dfc4ebd34cf2c21dd5a00abd039d005dd2c142e6f85ac6335e61d5912c3e96e', 'ubuntu:22.04' => '4ff59be5acf032c11bd1c52bbec7276f7dbec08d271ec1f580af76fa8f12185213640491fc218d99e754cd642367c261759002d3c49be531da20292215bb6746', 'ubuntu:aarch64:22.04'=> 'c77ac38d6c9d5e28beb0a96d2228efb4392a081d2a8495d729bc7cc8744c69f8a08ebd971b1e66ae83dca8aa84e866cc4f7ec27e1da4402f8fb72e732588f09a', }, 'icu_65_1' => { 'ubuntu:24.04' => '05b2a34ea2e1af642f5da612daddbc79e302ea9939ebd856cc0bf1e375ea2b2d481691c6745c3346e78f926ed1fd7436c271b3dd1a1c904a42c9c22151cbac7c', 'ubuntu:aarch64:24.04'=> '40390255f13f0607f6120412b66001f87d6a5dad562d2c458d094374ca8b42332822d163c3181625e2434e67d27b9ef90a4b80f8fbd80986359c5c90abe0be71', 'centos:7' => '4360152b0d4c21e2347d6262610092614b671da94592216bd7d3805ab5dbeae7159a30af5ab3e8a63243ff66d283ad4c7787b29cf5d5a7e00c4bad1a040b19a2', 'centos:8' => '0f3bc9c55e93956ce39c044cb99b4eaff8b69365c69ce845a56ff00ec32cbaeb84ccc9b37757f8024c7c7a1fffcc0e61ee4d8eeb226ac447a2d9718b5667e052', 'centos:aarch64:8' => 'ca3e6ab76206e6efb2d891ec7c9a540ffdc4ef954e46c6ca88f3c9e4ac6bcccf4e62f82eaaa60e9c21193ca5b574f9737f2aaa04034803f07133f65878b7307f', 'centos:9' => 'ebc4041781e7886d4c2526469bbe23849711b9c9b3e209ff16640dcb0d9c3c874a4958a6a4393c47b0ef8b188bd1ad74aff04ecf82c0214f6d7c4b08549e02af', 'centos:aarch64:9' => '30e9be25c9f61c9448fcd567420af034c2d51797630e6df596b2f6b8b0ff9bf72ccac83437fe0f576d229053ba45338ab0d2aa891c29b73bf7b31b5bd834811a', 'debian:10' => '1c10db8094967024d5aec07231fb79c8364cff4c850e0f29f511ddaa5cfdf4d18217bda13c41a1194bd2b674003d375d4df3ef76c83f6cbdf3bea74b48bcdcea', 'debian:11' => '0cca0308c2f400c7998b1b2fce391ddef4e77eead15860871874b2513fe3d7da544fdceca9bcbee9158f2f6bd1f786e0aa3685a974694b2c0400a3a7adba31c8', 'debian:aarch64:11' => 'a1926f3528067b9a5db29da4fcca52da62114e1f0763880595ad455ce6025788975325abd974cc9e8b17e0769c16211dafef91160e2d698e955769475766d51e', 'debian:12' => 'de03047ca1326fa45f738a1a0f85e6e587f2a92d7badfaff494877f6d9ca38276f0b18441ebe752ac65f522e48f8c26cd0cfa382dd3daac36e7ea7a027a4a367', 'debian:aarch64:12' => 'd1afb4ca6d0361614bafebb4ce2c36e7e0acc054d1bdd6dc6c516c908160d1ad9cb6d5b99d4792d4e8a9e9c0a888825e252d00d7c94e6b63bf79c00a1a18e58f', 'ubuntu:16.04' => '4038a62347794808f7608f0e4ee15841989cf1b33cab443ea5a29a20f348d179d96d846931e8610b823bde68974e75e95be1f46e71376f84af79d7c84badeea4', 'ubuntu:18.04' => '549423e7db477b4562d44bbd665e0fb865a1b085a8127746b8dbbaa34571a193aaa4a988dac68f4cd274b759200441b3d2a07ae2c99531d548775a915b79bb61', 'ubuntu:20.04' => '61b69192e6d96d5533339cd2676b120361031d41de4016ef7a013dab60b01385c6ae5427af74749848e2198f375b0d6585f0e63960a34ff49218b65c9a93e055', 'ubuntu:aarch64:20.04'=> 'c72f5910ec83088f8646368a543e040751b57881797ec9892a89b53d1bde6da8d3c4829a6b531b0c7d5c583dc526e9a4588e062ba8a4df70340dcee4a97a09c9', 'ubuntu:22.04' => '00f10b4edabe8c7415072432e55046633c3406c8aadcfb6d59dce950c7c0cbc116766fcd84e46b49415b1e0a65289cbf7d83282565e1bf37f38bc45c1812eaf6', 'ubuntu:aarch64:22.04'=> 'efd3e5a1090b9aa670a41ad67e2212d690bf6de05d6eb2c66fb9e7b6c1d9c5760477380a708172f4511e556a78749516cf7209a376322ed1ed3bbef749a014b3', }, 'boost_1_81_0' => { 'ubuntu:24.04' => 'ec51db04c9b98dce709a68a310ed283f3da2dfe4d8be6986b31fd84d3d26409f912e8e38d6456074c4a43c47ce5bcf7ffc4f400492520ebcd6fde15512c7a8b8', 'ubuntu:aarch64:24.04' => 'ca39e0f541a7ab34f38baf0ea67d6a9a018cea55fcf09ac3e476a1f85f0659959c4c388f73e00ecf036263450c80e193078bac10087946ad4d1ca0c97a1c4bfc', 'centos:7' => '403c89dfdfe3ef979f2f742b9a199a3031426ec6c10a0b1be895e5876240e5b636a33b590dc01766acbebe36ff9b6c7175523be2d95097ac37994a346081b343', 'centos:8' => '0e57552be3ac0d753838628d38485826aa402b2ac752ea1d546994bee3e9d689b3b439e652285f30f777cbc4e19a8217923d994079f243e8a3e4d4f354fa865b', 'centos:aarch64:8' => 'b96787606728aa663329cea0022fd33fc39344e82277e5b2869167e8f628560390f8851ecf663f4bec17181034493000c941e607ac3f9a484fe04c391bf1cc74', 'centos:9' => '3e9ad8d2032b5eda9bfe9a1a151f98545ae78cf6422dd307c733507554d4cd23a5d7b30d44552a15c66c0faa25ab2146fdf5a14a2cf360efc5c49ae17ddeb0f1', 'centos:aarch64:9' => '4e1d337d3193b6e0deddd8351729d7bfb145632b7b1c1388b613a55259ce0f20a3cb5e27c039f67143c277c39148e5bfff20cedc1246e8d4b13e7b73473a43a4', 'debian:10' => '3b146de940bf36ea301c2078edc8dead611c4a770643c548080ecfdf8820856b23fa73a15fcab0579550cb19ad816fbef6040ad98ee500a8d15a66ed99eef241', 'debian:11' => '6e8a48ce6874e5f12b1734e590c726dd53801a5193e71cc505ce2bf9e558318fe970bbac1c8e50938798e0c86a9314ea32268ce0e817cc4a6023f46fd6e011ae', 'debian:aarch64:11' => '75037fee14a63a62a26a7405a9922b1477b431187dedc9376de25addff910b99ea5e229b2169f5402b155d2a948fb0c311a9ef527756d5e1cb68205187af5d40', 'debian:12' => '9b4cf7bb2a002559b95f83487723d1d4f99277fc0268454367bc6912ffc41256a30c2e211fb66bb57e50909c535cefcedd611ab27aea373166db6c124d6a9d80', 'debian:aarch64:12' => 'bef2af4e6b7971dbc49a45a81502dd379ae2d3c179812398f629ed3d7e01c1bc4dd479323c3aa300c29a9f8c0c8a132da14862543feca49ad288d96926f188c7', 'ubuntu:16.04' => 'f9c9b6141d554529f8386412c20873758974798e646bdbd4a5aea4c35af8183057ae34930d3d59f296bd94db970fa42abb07555407d339f8aad07b1a2bd7211d', 'ubuntu:18.04' => '35092c1acad174667ca67cec6cc55b3a2944d194d16e669c261c65dc73f6e328cd7d0fe33e17d8cfd25f781e34f9b9438c8b1e0ccf24b546b17f949791082dd4', 'ubuntu:20.04' => '7e82d809ef02ceb3f9392cba59e11da05f90dcbdbec55f2d9b7280bfd987c5dfb3b7252d47cf2510d5474be0d0975e359146b1b2e6995bd0f721e707222fc27e', 'ubuntu:aarch64:20.04'=> 'b5c2f87237b7142200eba5ef45104a76110623075ea7fc29d483661a5635e01d2070cbf15102b621d76f030403a2ae4dfb7a0136739dd2211cf3461e7311c9c6', 'ubuntu:22.04' => '3fb3bc947a68dc84d6eebf97daad9f1e93ce21a8bf4286fd786e2fb55f78faef5c12969694f61e9255187618fd3d2e16ed96a17450a08a9cb41b67e18f025977', 'ubuntu:aarch64:22.04'=> 'b1df2e2ce44aaac8d0c3f66a1e9c9eb612e20836ddb815e6b91eec9ed48089752d8e5910a2b57edccaf06667fce04642e5867b36bf37c6614df07a3419877f70', }, 'capnproto_0_8_0' => { 'ubuntu:24.04' => '907ad5313a033cae8bb5320dfadf9ac9d8c4310d477d7e9a0570453a0696384706f3ac855b4aadbc5062c903d9ec72088f16db0ac04ff44d262762580a65b00b', 'ubuntu:aarch64:24.04'=> '536b931fd0e8abe22f0bab54f113db5689e2c28b6efdae786696436f0cb796c1a989b1f7a587afbd563f3333bf3f4faa477754d107d22ca04955e5620baec713', 'centos:7' => '5c796240cb57179122653b61ee3ef45ca3d209ad83695424952be93bb3aad89e6e660dba034df94d55cc38d3251c438a2eb81b7de2ed9db967154d72441b2e12', 'centos:8' => '27a2b5128a4398c98e65af1c00c7deae62a472b3b0c01bda96e6903d77974205f2cc6f1dddbe57cb39b3f503fbf466caf255c093d0b0c123e28850f517f0272b', 'centos:aarch64:8' => 'ddf4e19c0655b768a80c9e680e4ddb1cfe05fa606e704912a0b9d467e031605457d679756de23c13c935e9a45a5d770128d8d3cfc592d5ab758860f236dac574', 'centos:9' => 'a4cfb081e1b08b41dc0d51d62e9136826b313c65c773afc2942da09d096f0e07d109be500313ea6cb6d241ff5737f2d6b51a85526e8495afde45fdb2e89f8953', 'centos:aarch64:9' => '1ec4a4473e217eb6a0ae2d1c5026283f0f08811c70fdf14b658f467ef5ddfb31d18dffd7c763cf535288b6a3988831dbacc4ed58b69bafd333b35057d7eac6c8', 'debian:10' => 'e9ba7567657d87d908ff0d00a819ad5da86476453dc207cf8882f8d18cbc4353d18f90e7e4bcfbb3392e4bc2007964ea0d9efb529b285e7f008c16063cce4c4e', 'debian:11' => '72c91ed5df207aa9e86247d7693cda04c0441802acd4bf152008c5e97d7e9932574d5c96a9027a77af8a59c355acadb0f091d39c486ea2a37235ea926045e306', 'debian:aarch64:11' => '82b411ab9a2f80415eb5c396d933a88aa9481042566fe1708b214d087a77bc67e17d0544c721f3f6b254ef6685a843edf71aef0ff44f697bb22417279ed1bf11', 'debian:12' => 'aeeff7188c350252c9d1364c03c8838c55665fd9b7dd5ebea1832f4f9712196027bfa0a424f88e82449f1de1b5c4864eb28877d2746f3047001803974bd1e916', 'debian:aarch64:12' => 'e72adadc01ddd626f398992cbcce291432a587a8e86340ece77524b5a1cfb0ba354445c14b3422c501f914c700fe78a14e1d01bfbd9a476fc061cb7bc3bf93ad', 'ubuntu:16.04' => '5709dc2477169cec3157a7393a170028a61afdfab194d5428db5e8800e4f02bd8b978692ae75dae9642adca4561c66733f3f0c4c19ec85c8081cc2a818fed913', 'ubuntu:18.04' => '3c1281ed39b7d5b8facdb8282e3302f5871593b1c7c63be8c9eb79c0d1c95a8636faa52ce75b7a8f99c2f8f272a21c8fc0c99948bbf8d973cb359c5ae26bb435', 'ubuntu:20.04' => '916ee7622e891517b35134d3418dec0e33be54f8343418909f2659bb11f41d96a97d61c02fc569960bc4dafd5e11a2f6f7a22d7d3219bc3ed49c47ab6b47f5c7', 'ubuntu:aarch64:20.04'=> 'c0ea2e83a1d5bbb170a9432933854404b25d7af26c860057548e6fa67e8b4372fcc82895e982dcfe1a835439a52f07300d41457e108657e00158713daaac0135', 'ubuntu:22.04' => '3260ffb9dc13aac6e045480ec3f9b7cdefef30b1446ca298ab6b3cd8628192f1bb6422b8c02a7fecbf5d65038dda3985cc40773c699c49b01afcd50d1395be9f', 'ubuntu:aarch64:22.04'=> '9f29fd8e1ee6065929c0fc6ab1c39e924a4a9062d4a2fdd060a03885f26d30927d538e6d7cdfea21f1577fb6259c53e7bf25148ba250226f0dff0f6dcd5e1ece', }, 'hiredis_0_14' => { 'ubuntu:24.04' => '1f002880ebd59952ee806c107fc200c1a49b69343386796968922f36e39661df814a958372544ccc0a051fdb3aa4fe053636016a74fad9521bf905ef5da41341', 'ubuntu:aarch64:24.04'=>'6fe1ced6859c750e05250af05ea386e0166c1b3c54dcd5497310274e8859a8cd7fdc2a298182e19643a3e3c48ac3eea385ec4ee74e7487cd6b159383d57f47cc', 'centos:7' => '03afc34178805b87ef5559ead86c70b5ae315dd407fe7627d6d283f46cf94fd7c41b38c75099703531ae5a08f08ec79a349feef905654de265a7f56b16a129f1', 'centos:8' => 'ccd1828c397ed56e4ea53dd63931bddd24c0164def64ceedca8d477eb0cafd7db12ae07a4da9584331b1af9ef33792da1cb082b3a93df9372df5ad233c5f231e', 'centos:aarch64:8' => '18e39412a6c8fd9bd7f2f07858784a2d7debca92ce71290bc2c6269443f53919d29ff3f3b6785d637a893da3bf0f409e2160297007546a46984ad5f3980c4ed6', 'centos:9' => '304e402b1a86734095476024840c0ba8a0ccf98771ac9655672671a7b264ee73a87a2043ce96ee8acffa12901f75ca5403dda297c040b8b3cfd220979df472c7', 'centos:aarch64:9' => 'aad8b76882dff7b92891647ed596e0e4ac12930f37f7a7ac4f81f6600c0e289ba0adf82bcd0480969ea1e1dc66ee9e9cd9531227ecd6ca6757bc34337a4c187a', 'debian:10' => '76ca19f7cd4ec5e0251bc4804901acbd6b70cf25098831d1e16da85ad18d4bb2a07faa1a8e84e1d58257d5b8b1d521b5e49135ce502bd16929c0015a00f4089d', 'debian:11' => 'c0effe2b28aa9c63c0d612c6a2961992b8d775c80cb504fdbb892eb20de24f3cb89eb159c46488ae3f01c254703f2bb504794b2b6582ce3adfb7875a3cb9c01c', 'debian:aarch64:11' => '5fd2dd3afb0f85917b6c0760d7b97dc803d30caa3105f1bf7c7119e025ae4a843ff1e69507980a178219e89d246c586cbbf8dbf0f0168946d5cc1c2ababbc81b', 'debian:12' => '0fd3ccfecff6eea982931f862bdfa67faf909e49188173a8184a5f38a15c592536316a202a1aada164a90d9f34eca991fee681d3a41b76a0c14c9eb830e60db4', 'debian:aarch64:12' => '81d16b6b58d785c6beb7444cf75523615511c19316f5c1d5b019524d31db10ca17e4da8b5bd5a5495c493ca5a0c10ccda71240e4d85561681070b0c525003c6c', 'ubuntu:16.04' => 'a8fbcbcded98a70942590878069170ee56045647fbd1c3b1a10bb64c0b4bb05808d8294da10a3d9027891fa762faabdf0a4b70a72a10f023a83a4b707b9a7b5b', 'ubuntu:18.04' => '5171604d9e0f019c22e8b871dd247663d1c2631a2aa5b7706bf50e9f62f6b1cef82db2fe3d0ff0248493c175fb83d0434339d7d8446c587947ab187fabf5fff4', 'ubuntu:20.04' => '0e424f586b402f83fcca02ebdf11dbfdd6885788c7364c8957970e33c5175093e78d754d1a35f893744c8e067d20267501e73c18a2ece6a2751c46b954f18f8f', 'ubuntu:aarch64:20.04'=> '786b33655f8f5e18a1df177482c88b36954dc22e3ba35a34342334eadda5ee9edc1e44a3134aa560b5ab2b2d3ca193056848568fd8868d968b1b34e0134b8762', 'ubuntu:22.04' => '62fc6659b6ae7e6d6aa573cc810b8c14d01dbf1153913ae8a929e51676813b71ce38d77d7f7f8746a3ccd8c303306c8d0a5cd84faffb78880be425aabd90e200', 'ubuntu:aarch64:22.04'=> '3852674e4d8fb58ccfccda3e1509216484a437155f70590fb2b5bc45c41dcbfd2adc16095424806335ffbf4f285588defec36fe97b1edd8baba628daa70b3ece', }, 'mongo_c_driver_1_23_0' => { 'ubuntu:24.04' => '0671e5bea1a03ae95a02293683c2f3cfb11652285db5e8a1a2148299a6364d942a70383a9fb0499e4eae7f1f7dae32b2e4e7b98632bf0f74aed589f2c1694bdd', 'ubuntu:aarch64:24.04'=> '09da4d10a8445f27d34628a4ff2c24a4e6a95058cd86d46d414df35663e43565036d3973f71897cb0681e68597a0b905f4fec4753518afc71601d5a2909af4fd', 'centos:7' => '8ea15364969ad3e31b3a94ac41142bb3054a7be2809134aa4d018ce627adf9d2c2edd05095a8ea8b60026377937d74eea0bfbb5526fccdcc718fc4ed8f18d826', 'centos:8' => '99f69f62622032f4f13dad5431529e4f0e69f02de0e23f74e438a2a3677a61a33e649b7384b242857401776a2162aec9478b5ce3658b9ce0b9e27f8fa61f625a', 'centos:aarch64:8' => 'a33fb45aae3fba3c0f2c5e5efdf9e8f1ba09dbefba0368f0316dd536130384fcdb7c2248a38adbb0b3594e31c7f66a897a3a5571a51c537b7f25c8b6232e5cc8', 'centos:9' => 'b1785b2bf23c8363856b8131732577ca955d66d346716a6d5c2f306042fc25d4cfd9ba320dcdcaf34ec810b1eb7be585427f382acbdf99589b84dc246a85871a', 'centos:aarch64:9' => 'dd575c403085190ee682074c8a826ee4c38ba91786bedebead80db149df228a7a80ffc93a9d13167f7fe29b2a43d653f4f6ee3123edf52dac5a20b1058d3ba8d', 'debian:10' => '3beadd580e8c95463fd8c4be2f4f7105935bd68a2da3fd3ba2413e0182ad8083fd3339aab59f5f20cc0593ffa200415220f7782524721cb197a098c6175452e9', 'debian:11' => '22be62776fcb48f45ca0a1c21b554052140d8e00dd4a76ef520b088b32792317b9f88f110d65d67f5edb03596fb0af0e22c990a59ca8f00019ae154364bd99e9', 'debian:aarch64:11' => 'f3431068b375c7b1746ff0809ad41f9b0e9a7b28499420dd432df8d5cc3c9545b1046542959f4b9ec9ae32659d9d4beafbe8ac735e35b0fdf92c169f90f1b6ce', 'debian:12' => '394478b115525dfc0886b832998cf783d7c7e6a6ee388af2482a9d491a183edeb791ab193ddb84b50112c532dbc51c34c8cad597c1f5f46635a280a03dbc9f2e', 'debian:aarch64:12' => '3329f2d800509a4c87a4985994952951fda8ad1c0425ddb963ebf16f5a2138c5a4c7b9353eede89c5b23804beb2d185decc1d2e489cb63774a599a761dcff34e', 'ubuntu:16.04' => 'c61b7c5e216a784eb920fbda887fa3520079073678c878cb5cb556d7b01d7d66f7a3d33dadbe2b386e0e55deea898d85540effeef930f622c2a10eaf0291d7f5', 'ubuntu:18.04' => '556e5036aae67cc994054142dec8b2775025bbf8fdb27cdb6434b5fb5b4cdda2628829cf8a764c31e71bbf613e4dae8d8382a16fb4f66aabea1bf486b500fb27', 'ubuntu:20.04' => '319768e46ae4a308700033f64953966ac0e6544712d683305b6c5621542decad3e240f22afb7b83bacd6bc90766be342278de3a3643e092cf26ae7d784acfed7', 'ubuntu:aarch64:20.04'=> '9a71aaf2197b99f3fa00466441802a57c4aaa29131673565830c4d0bd3719beb9ac2918b062ff9e0a81949fecac6ca2b637780b1e4f05c91e7e92a9e7dc14ce9', 'ubuntu:22.04' => '4661c4c0c75d54d7e07ae069b8d2d1174bf06df95c1a7999f927e9b29ed251bdb035290668ce24efe60dfa944eca266e43655fde6873e5863951d8d21bd629a6', 'ubuntu:aarch64:22.04'=> 'e0a342176ccc26bf2a978f160b04046cf5a1bec35b7eaf85574b3f76196f29354dbb8eabe86326d7a1f93ce6805e18127462f7048ef7e8c7d226c1d04e4241ba', }, 're2_2022_12_01' => { 'ubuntu:24.04' => '2a0c6571f5803fe2b09bd2f5100a4a4b625d1a3c86160a4a77080f9ec5ca37a9592392e1026e516cb544bcf1b9c8c31252d2dbef040ad3b22be52731b63e4703', 'ubuntu:aarch64:24.04'=> 'd5491441bc7940d4f82bc7e503382251f674cde393d2ccb6e36e68b496761e7946bf8988beffc3c77e4527d523ace43adf9fdcac0cd765fa7a196d88a914af06', 'centos:7' => 'f0df85b26ef86d2e0cd9ce40ee16542efc7436c79d8589c94601fedac0e06bd0f84d264741f39b4d65a41916f6f1313cfe83fde28056f906bbeeccc60a04fff0', 'centos:8' => '57a0442b035767f163559b31a76c2f59285d3605ff078b17aae45a8c643d22f7876ce707d0d2b9b23791f8059921b5334e9b6860718e46510a0aedf05ffbaa58', 'centos:aarch64:8' => 'e9ec5ab3a71770d77b0b758167687f9ff923e75388372146f11ad4cc3bc3cb24990f77ef4fd34c81c1eefb3674160f69bcc0775a4d151da9c4ab9538f661afa1', 'centos:9' => '24ceda96377155a57fa6161def14a8dc7daf10f825fb97f39fcf434fece8b4efe7523e91cae6911f6b58753e73e47547a01488498fd16e7b4c64764682a2313f', 'centos:aarch64:9' => '7d4210d404b9e0e244cfaf234a09d81bf15e0c63f68159bc90afb4c732a4e1e5e85e94e5b1a2dd67f9766b47f28c9a8e4662106722b2ab95fd5df9be1070021b', 'debian:10' => 'f2f6dc33364f22cba010f13c43cea00ce1a1f8c1a59c444a39a45029d5154303882cba2176c4ccbf512b7c52c7610db4a8b284e03b33633ff24729ca56b4f078', 'debian:11' => 'd64fa2cfd60041700b2893aab63f6dde9837f47ca96b379db9d52e634e32d13e7ca7b62dd9a4918f9c678aa26a481cc0a9a8e9b2f0cd62fc78e29489dd47c399', 'debian:aarch64:11' => 'a8356a05a5608671f89fdfd4521fcf560885efc77d57f4cf13c02f309ffbc291f4365d8f3fe82915198c1812032d287300a6dec9ef6c72bd6a6ab5c99ba6e8bb', 'debian:12' => 'e74d88c10c58f086e0b6aad74045321d280eced149109081971766352a0d26460e253c6193ce70d734a73db4c8c446a360d6ed0ceb1b40f2cd52cca9660e340f', 'debian:aarch64:12' => '48fcfd4e912d909ec65360ffbc53ac623d351384bdc79c92345637bbc07f7f5c03e3d7d47832d2478c12b638a2f23e52373f8d09fbc9c49910af3d780d940d56', 'ubuntu:16.04' => '39a585e44db8df7bae517bb3be752d464da746c11f08f688232e98b68a4da83a0a83f9014879d8b837d786f021f6e99cf28fd8cadf561f2b2c51be29beb1974c', 'ubuntu:18.04' => '405aa75526b0bfd2380d2b7b52129b4086756d1507b04ea3416c88942c24b32e6a0c6893d9af3e3903e766fb2dbe2b48fdde7e2f355a651368c810ff3c1d2cb6', 'ubuntu:20.04' => '865f3472da0070fe3aea5fed5f98d1e6e42c145045941be19eb324d39ff558f92c281194760d59c37d7a306d5d59e08989ae35ba63d3624981f20bd73fe2a7a1', 'ubuntu:aarch64:20.04'=> '064090aa565d7fa244819dec20939b4ac790d8bae4b5cbc160249bfb3d50b6d52c447c3483692e5bb2577c5d6d863a05c6eccb309943c5a49d90cd603be4bed3', 'ubuntu:22.04' => '6cda2a6653881a8a06af7f7d4b8e16be09bbe5e3ce463a2e62d8f479aa028e81e7eb8489035b55dfedbe5dd223a27f4673337073a216062d7499992153448a5b', 'ubuntu:aarch64:22.04'=> '530bd200ab98edf4fcaf74f407ff53788a0de676a5a383eef067e819263945bb5e883029a7ae4596b72542e6b7a17af3128592cab8f519dd6d646cd7e6bd5b54', }, 'abseil_2024_01_16' => { 'centos:7' => '53449e6d5448ecd21156b4a93b43b1e94be9578c21ae435a4a737920d94cb2a423301f9276122980a1f93e21c47c75feb9abe2a090b460158c30e8ba606eecfb', 'centos:8' => '3a03f62a9ce0a0176602161da1c5f53a827784043deb771bb211fc33dcc39e4435362d30ef8dcbc17b4936b3a30c7506775d6517fc7c691fb422ce333e277cbf', 'centos:aarch64:8' => '920957bc324721ec5f6bb9426babead4dd9b5feaebb1d4763e717558f26cfc857e3b86ff73f20f9c8720fbed5c1b0c103df2d782c4e69fc1f3c4717178b575c9', 'centos:9' => '3f93b9dffc046e6ee0a74ba0e0eefd44d6710d831eb3ab3250617f918770c1b564267bde806c41c30591b261da5ef03cbfa3d3a48cc01d436cc5c3d4fe0ef5c5', 'centos:aarch64:9' => '902777398c214c2ff34df3f264a24d8c95317b83a79923ad722d99275b49529ffbb55be90e149fa99719cfae3d07a37461665b5d18b729195fbc42d5eaa393de', 'debian:10' => '65c553eef7902fe25742af0030439144fd70d84d8b5fe0b14b9276b560e04f9b86c5f1e896b25f8ccc4b69b4d5fed4175b48b6c81eace1ee21289ffe57159e7f', 'debian:11' => '96c335b23f82aefc81bd4f967822e8852d01721ecba3356cd46c4a452f1a121d7c53b7e9f6145bc1c839479b48f15a538fcf23c5c015cd2869404131ac25a232', 'debian:aarch64:11' => '5a1f9c0a2c86b3d1a8d53103c77d49708aac4f758342a1f4107194259ba734407b4f9ae829b3fc3e42cdd08395d688e73722b0a0343f49a51fcb8bd2abe00105', 'debian:12' => '9c4616429747e243e5b072439304b6cc2108fc501d455ff36d7d21f144c821818592ae1642183829ba7da28737cae284a8eee0e5c279e95111b51c153c082edb', 'debian:aarch64:12' => '34a75cc20e47715250474bdf1979ff50b67ee2e676551b37be412eae141286866b52a7a1108b2c0315c25bac2f53fe180249799983d3298d184465c89412275f', 'ubuntu:16.04' => 'c8e0d9c4b3c490162aae93052e57399a62d9283d7a78aea282693413d7e84d27f9810bbab8118eeca8d33422ea85d0b728b762d6d8e8640f72c69fb1ae32dda8', 'ubuntu:18.04' => '86995e505ed01ae97d1e16b1541b75d3f138f8567348dc64a56052bfa6bbe0bff1d82e2a2ed0b439d114b109213f49040bbebf3216dde81ec5f990d6b811a497', 'ubuntu:20.04' => '33854a60527d1e9ffd2b38c32f7fe695348c1ee3619005aa288f1639c79dd6c8ac52f8ea4b43a8cdd122ea784bb291cf9803c326610a9ba5bf2739ccb4cab317', 'ubuntu:aarch64:20.04'=> '0a99de44ad0d94f8d75202a127a84a9eab7c8a2a7095546f1e87ee55745e45fe8bb4ce023ae7effe676c4a2c8c2a67d644dcde6bee86f72283ba32b76eb1294e', 'ubuntu:22.04' => '2a4f531d210946c806e382765abdaf85713cb88d491dc7052c0ae4ae188d25a09f5cbf71f487eaca1dd9eff1bdf9e472d464697f5dc8b0e9405de6820c07fa32', 'ubuntu:aarch64:22.04'=> 'd5881f8ac7842109fd982bbff7bf5ba7572c41b83d4c5b5a659e2019cf3265202057e26e4453345316e9c6c7f21fee00d7ef2b4069a96af434305d14f1aaed24', 'ubuntu:24.04' => '1ad4265bde0cee2a25c2a6585277712476979e82245ef8784364804215e1864b2a5931f410c85a65e7aee47b2db494d58e2916278689c6028ff95d1fd56e3056', 'ubuntu:aarch64:24.04'=> 'e322a686c54562f1369fd02a9f02929be91792acc02839f78e872cfebc8c98827cfcbc038184913716bf4f6108b31268307af5db161fb6e6cc2a05fbb9b2f077', }, 'zlib_1_3_1' => { 'centos:7' => '519329e00230316457b662bcf218ceec3837111dd088766232e8393cf01d4a0f008f7cb2cb6a4153b0f5867572c340f71bbc96a6bf0f675dce273d5962555ae5', 'centos:8' => '7b44bd3b8ed348176f60cee201c38c9e56cd9e4b51807d9d4aa9b60e06238fbb3f5e4370b68bc501770f8f7e05f672b302857addf39d88fe8ea1f315914a34ac', 'centos:aarch64:8' => '710c99271c42231c516a65e5cd311543d65e511a8040df294ece7c8cc4feefdfc81cca6ad4cf1d496193d2cdfebebc90b93c1fa3514899c9ae954baeb4f1a2cf', 'centos:9' => 'b61e8aa399303aab3b5f9e61efe5a0b9bcc2a7a15df714039af8eb5027be7faf98c3ae6220102480809ec8ebac8982a87549d13e8788756249b4eb17ab92203e', 'centos:aarch64:9' => '846290fb44195f2882718ae315748dfa73ebc22261836a2632f2b897bfb3a95e592c3bac18b104fd5181d865db8e7e38235f3996854670eb688ea68d7a09b639', 'debian:10' => '27fbd2fbf52ce0bc970d7e963bc2aa5e757ba09ad38765283c31b298ffdce97aaf7ecc90563fa110abeb535ef5d13f69976b5fbf731adec9a6b8079c4aef7353', 'debian:11' => '92cdd4bd6ab80209bbc6aff8c116213d36bef7de6164f186c7f56fe77d58674edc975ca90c67b20767b303c5ab5fe689fc0944c48e560dda59d2bc68055beb45', 'debian:aarch64:11' => '3bf8452e020ef2e623a4410b592bcf1d9ea3a2968c335eb3107eb880f249018d304e9f4c490207f0e5c9f3fa7901a3bb0eff1e73dd163aa2f4cebd1a23981805', 'debian:12' => '54fb9181922cc37f8df35e6a0183bddd2ff36ac6b8b7217125e8086c1e4812d46b3fd5fd881e965beae1b2dd7aeaf7406f0bba4d732321f7f3fc51be257fd91c', 'debian:aarch64:12' => '7c36f0f6e7006bbb99786f80af843e517c9838fd022eb41a8872c47309390cebb4740e47ef71b494245e9b260e0ce57ef9da3676a7d00fcfbeefb87188e75fff', 'ubuntu:16.04' => '2613ba3bc6bc4753ccf6c0399695a45dbe776ee1db896f7875045f271e81892134272dfc7b0407991c5b64ec29adc732b53c579fc09a9f82cca2f61ab684ebb9', 'ubuntu:18.04' => 'd541d0b456aea4f576ac686ee3878e3dea7da95571841c24ac14570587e6f7ae5875508901a94f42b2af613bb29d599175b4b66588f18310a03c1a89b866f3c6', 'ubuntu:20.04' => 'a61b4cbf384c445445bd778835153b372e76698a1cca281004c3cd89379edc5a9683367fb96afa445795575fe8fa9dbe80ab1e8954ecea57eecf2ffaa4b1fb5d', 'ubuntu:aarch64:20.04'=> '3fb900aab3ddb375854909e854851ff9423fe8029724083c2e38853b7785a1605765509bd1fb20b7f5b74d8b0c08624ef656630374f0fc855a659f661133d480', 'ubuntu:22.04' => 'e0f52449d2e2ec8a9634b96ebf6fdbc9cc062e34cb6a2037a3b8812a9f2882ce3909e721f580851a3b3b986094b2c5f9cceb6e00b87dd0366ff8d5c028304a71', 'ubuntu:aarch64:22.04'=> '62cf5507e691728868a007aafeb3a951155ea690db8afa505ae6972583564b671d37b3d5b1dd5be90a54bc911c351227492180182f527774792d58830f19245f', 'ubuntu:24.04' => '5c4248ef746369f2a995cb7a79d21eb4e4d55b06a824c2fbb5ee4a195462fb96fbafbdb719bdc11a2b3e7b67e09b043ced83842a421006ad9bfa45defefd9b06', 'ubuntu:aarch64:24.04'=> '0c5a58e9349acd0250b0fcbc5c39f095c14806ab0743e0650ccfd831e5f8f568517143ba24ffb97401be2743f7d91010c46268cd649acc2bb79c169ad3ca8ac4', }, 'cares_1_18_1' => { 'centos:7' => '65902575e20b3297a5a45a6bafcc093a744e4774ea47bb1604c828dfa2eb9a8ccd63cfc4a2bffbb970540ca6f5122235c5e19f10690d898dad341c78a3977383', 'centos:8' => '6fa9349119489102decadac528b040f9919d6837a00deabb5e661f2c4f41d6c78782b677e83bf73179ccaa59eb1e85066e0bc06313e6bc0013b6f076e03496e8', 'centos:aarch64:8' => 'c3942c442173127dfb99f08ba21c699c0abb19108703842452f54b2d8d89cb4a8c636845e8746e4316ca55388386f39f53cbf44ad49cd6cb173461dcae503dc9', 'centos:9' => '630f37a4363b42ca2256a3482009f6535680efc504e6df5ac2ea07104331803d14bc610e472b6143127eccb3315bd90a4961c1bbd13e47f2bf6be14000b3427d', 'centos:aarch64:9' => 'b5b4ea12176f7db5591c862077f621ecaaced9c276231d81c189c8262b978f68d084e6d6e2b2fcaa488c0c1ae8fad5a9b36a861c91f77569f722f4c413617540', 'debian:10' => '433fdaed84962575809969d36a5587becbcf557221b82dfe4c65c4a67e6736de0dfe1408e1fb8859aacf979931a75483bac7679f210c84a5b030ddeba079524d', 'debian:11' => 'ac55ef15730576b2e4c464775cfcfd13a67c497787d80719d524992585af9f78271d1a1e80cacb3c7566ee240cddf459cae933621fe8574d906be708fd23a40e', 'debian:aarch64:11' => 'ef28a53a611b3dbcf0f674f85cac52fdc69d897d8d9780caf1767d24b363d88e177972927632508dcd92946f582f506d19aa1e030a4eba05d65cb19d4a414e07', 'debian:12' => 'abd0c3872dd7b90f853931bb5a50dcb3f84acfecbc04e6796639ed9af240735485e8cc0e0fe2126e857a2f265d73edc4c9a0842afa0964b1522531ee56b7ed2b', 'debian:aarch64:12' => 'ba1c399fc7310c2b6c5b3775f81ea4c30bd5d43a66f64504920dd53d242641bfe86ab47a8ca093667f40da35fe898225d9dbb9d81a95ea88018fd9932345479d', 'ubuntu:16.04' => '8ad5c1e5cdc36b609d163cde39f87d99fb8281baf5c692f5525c3234dc8788f8f50f400cb4e2ca0e089c19275167d20e634f509d06c4d61a827ea2e4d3719dde', 'ubuntu:18.04' => '58fdfadf9492cba13e907f2227d3ed249de78e8839ecfecc01957d3e62c8b4c5969ac647068ee87e84a44ee853fa88367ba29fd8dab6e53a685f31e699148ee0', 'ubuntu:20.04' => '590469366f085d5e996e6d5c6bf1eaa27c0b209c16fab7ba7bbec71dec44cf4522e1997a72bd48daee694b6bddb0415ded4b3ebf4921dc903eab5fab0997feab', 'ubuntu:aarch64:20.04'=> '88d8f14e0995e017315801fc18b2d8b0ac9bdfce88889b715e13c7d61440cc0eda0faa4f407997248c1c00110ab213e82c88e9fa27de58aa55fc977b0f06b71f', 'ubuntu:22.04' => '4447ff5648ddf7b96365be52505f585eb1d51f1c8ab2383553073239df538a5e5cbfdac04cf7998d0ef71bd1f201113bafe52d052dcc57b123132aa3f7011ab8', 'ubuntu:aarch64:22.04'=> '957d26b5370f40b0873d3d0b273496688df8130a7e4568db57171e418e59dd27685cc81922c89b890b817269725f0ff49cc9511fb77203df3a32e51f6891f79a', 'ubuntu:24.04' => '9de0745b8bb43f8dcddd0d7c879902a140b1063d5ccde3e53e365d8e037eaa350817d1f7314d7f995631e1451b216288c35f029cffa48c4c4618eec61384633f', 'ubuntu:aarch64:24.04'=> '66c0aa0f8e39a9c6732ea789f47453adb29ad2ff5af0869e74f9fa6900c67d116182d298d2802c5d6864e7ca327171af5d0cff08ebcc05172342ce7621100897', }, 'protobuf_21_12' => { 'centos:7' => '82ad83b8532cf234f9bbc6660c77a893279f8ff27c38b14484db3063a65ca15b3dd427573daf915ef2097137640fa9ff859761e6d0696978f9c120cd31099564', 'centos:8' => 'd0819b908a3deccab82aefdc50316717eb0118666c919b23638d2675b1e410f30ef110f3c687b7df709b05121e88aa9dd1a6a06ed675d29544f40cf8669bb7a8', 'centos:aarch64:8' => '898f6e67734dbd0bdb4a89f2ed10330d45f961edbb527dc67f424e886162b80316e58b64d66c5f381c64f46f1f49c550fbb5f50fe620143a86b5d9b172ae44b6', 'centos:9' => '2ece586671958cf8d619f6da6b6102e1ae36ffc15113d0fe9c3b151a6b15bffbf2ae957c4b74bfcf85996b6327cddf120bc1951b68376bc5d72c5609f9b63b9b', 'centos:aarch64:9' => 'f90e7d3f23457cc13ea19559aca093ab58692ac81f233bed355f4a0cd9c7c8aa1ac44c5e336b39d807a2f46386368a6e9617728479b9007aef3fdacf1a7510b3', 'debian:10' => 'b0dde2a94dc7e935f906608be5c8204393e87ba5703b78b84ad41ab690107eb306c50c9572669b0d18a55334ba26ed22a2242b54d6e30dffb1c11f8328b23c20', 'debian:11' => '97252bc39c9c218f02f1c5d1020296497934b1fb2ed9300c133db650ef327cbb06e5fb985234bc00ca9e86239ff2dd28b844a0eb92dedad2d4a3d88e47984caf', 'debian:aarch64:11' => '275d64600136495ec93288b0a3f70d4447dcfa0f4a92c0e42aa5930acfc8ef86dc114177f7a6f0e1bb939eea9cdc4bc8faf97197e9b9ab447097d49dfd441240', 'debian:12' => '8bfe22a6dec32bc56b2c042424930c18b397696fb8d67001ef38f93e1b7892fdfc947f1be1d697c9ddd245b56e029080535d385a663dc61c1f6bf959558e28c0', 'debian:aarch64:12' => '144a7b6bf0fa8a6da23f05465e8d32ff1c53b33c8db092cc4457a2a8c70355f6fd8a630831ba0aa87f3d04b2624263de302bb7a95fe272ce57866f45ac329d6b', 'ubuntu:16.04' => '3bc1b533b0e67b1c3599a63510a9a95b170029ff74cff5d1960028b770631c5b14bab38e39ab0aaa4c9a5e8bdc295b671dbca2aded7e5fedc99179f8b1a0f83e', 'ubuntu:18.04' => '77ea38ecab665863631b2882338a7668e730d870f02377c4ceb10af1a3f91d35befd110790866ec8da6e9cd9e2cb4b88a078487b668d397b624cf5e9e2bd9282', 'ubuntu:20.04' => '3f4bccf529d54bb3dbffe3dfe7ff95a2f1232ac102b69c7034fa7b3254bd99ba7fd3bddf5679678e2365c0fdb96251021fba38da556fa62735743a17b8eb0b9e', 'ubuntu:aarch64:20.04'=> '59469a1d99b30eb5472d3d595180319213e4ae3c16a209d719a65cba2176f5e398add6559d4a3a34f033477c268e8b96fa19ae3c45b7349f4d7e10787abbc040', 'ubuntu:22.04' => '5a8b91f98531d91fc5836ca63246016ee1be0dfc50b51cbf2cb6e8c5f84c3716ce6700aa7d30b33c986407a25dc8e39aa6e8628fce85f1947de49edb3ba5c211', 'ubuntu:aarch64:22.04'=> '4ccf53133017195dfcb16da67912ea15ff6c8d2aa8530d808f971f6854fe562a88a46bbc4fb99965add5f3054d2f5fc24f4032feca5b6124cae4807a3c8923a2', 'ubuntu:24.04' => '4e37724689e7fe173d628d48d7bb7b3d41fa26071e583c5b01d5873ab0c134e0ae37a6e429c55f55f9b91fb98ebf9896607d475e12a4f1ee1ff92eef4730f0f2', 'ubuntu:aarch64:24.04'=> '604d2d781b5811fcca1a536f737ee7abd15583052fee73d02a85dcfa2613db2580bb32b3a4dd5d1a29afc193f10be17bf4747e586f8a7f8265b46d6f60954d7e', }, 'grpc_1_49_2' => { 'centos:7' => '0641b6cfb15da28ffdbb26eadcafebab238b9a2769e36a297718bfb6cf4518029ea16edb716e7b89634db564e63d8d636389c12d0e85a25ddc726ceb51ae1843', 'centos:8' => 'acbaea6e9522a860ec11d59c8a28d54f08a8d8d7400d4f14c9bbe711685d612f9f5aa063b07bbf8ce9b886ccfb295a2fde7f0410d4ecc9daa67e03c4f15fbff0', 'centos:aarch64:8' => '458281ec94a6421cb43f2fdb0ab3d5f0a4124f6cabcaa53ad3fea65236149eeaafd117e8471605d4fb6ccd7727b6a9c874fc977db5ac59f61d6a2075a923ee91', 'centos:9' => '9bf7bc55e267d3d70ebe784391f955e3f0d72c23708b542d3786944e55936eac0291acbe4fe2bcd36545329aaa27cc105016e3cfd4580130706d8ab55c919fbb', 'centos:aarch64:9' => '33d67cdc11e563c64f810dc446232710f29a09704e8dc5618e1b45aa6cc028bd6e8f43337ec07a614c2f2b8680573e5a78b6dda243fdbb8a2e41e37ee2bcf8f1', 'debian:10' => 'f62b27dafca95ef4bf9ae4177b3d1ac25cb17d5e6b64816694ebc234b3653e64555a953398f86f6652f4ff2038d81a5cee4b828472bb900155999e62c2a9b128', 'debian:11' => '327548fe0b1b5419779a4ee16c4e09b5b6cb19af397720018d8af7755c33850693b1e56c4b422654f0c56d25bcd2d7230cf630a7034360a4315d853b2851b636', 'debian:aarch64:11' => 'aead130b4e3fc1bd846ed4260ad54bc6ca56bc60aaef32995074027ca049ed6010afe66df559e1276f37cb112321ee7f56c7962f54cf5851ff0973e4a3a2ee4b', 'debian:12' => 'fd96041122531ff450a42d59110fc729149f52178e3922ccfa45abd3b91d53b57f250410282a3a26f1e5ddb896d6ecd6eb4d23c3fb3bd89423427d5dde5ff970', 'debian:aarch64:12' => 'caf23a0e9ded1d337085f1767e7b44d454135f28b4107447e78054cdfd825570b62fbd773b00ff979656557aee596b9eb8ebbf9e2347cc7173702bcc6802219f', 'ubuntu:16.04' => '8f7e3ce183657660b18ae6b696dd4f28270fec39b41d70a3a66653f93da43f42bbda82cd8b7158fc5d50b9baef91e4a9c69c10b1e0ad61477341efa81e2b0987', 'ubuntu:18.04' => '7d1a57b256543e8394be998d2da67888bbcf948f43cb451dc14c37fc8a5b4fb5811d1a9b079e1f3ecd02744f78a32f5b52315b1b0eded6a405dfa44fbcd19b35', 'ubuntu:20.04' => 'f9f7f6c803fd948d6762cde4c0471c414b455a8e1a04206de0596050bd3b2d6105d3d4172e09928581397536a3680b5109a6010ec4f92dc1031f6c872eae1def', 'ubuntu:aarch64:20.04'=> 'f4b9afe30d9c99b1ee4d50237e3d4fb365ebbeaa02e287c18e1278c6e31db6e5305e96d647296fe6addb7273ad7248ac91de2b47ccbb079e4ac5a84256a9e3a5', 'ubuntu:22.04' => '4b353beae63e58c98530819f5e0645aed0117c52d7a351a3dea05086cfb757b136442bc064a9bc87c46f29017b77b28776a1514f322c5276cb0eb2d926b895a5', 'ubuntu:aarch64:22.04'=> '1d0643cbf1f993dbc9f2cfbc31e865dd061c4f0cb6d6476ae0719186e502ed6e5e83dc8331a86ef2d3a5beac02f5d623844a0fa434ae67801794a74bd6cde5c8', 'ubuntu:24.04' => '876dc319e9aab49ba268e853adb9a006b539bea00d8c1ca6d599a6892f525db41369252470c9454ecf0da154e82389495b6c14e4d7c5779a536b59731bc76af4', 'ubuntu:aarch64:24.04'=> '100465fb2031673382de62b341c51d543e13e07bbe4806a515560068bed4f157dded45de591c0a33d8c6470d1afa28525003ec1a6b5fcf0ead79d9592d0e8987', }, 'elfutils_0_186' => { 'centos:7' => '23acf9d80f72da864310f13b36b941938a841c6418c5378f6c3620a339d0f018376e52509216417ec9c0ce3d65c9a285d2c009ec5245e3ee01e9e54d2f10b2f8', 'centos:8' => '28ffbc485b5feaa3ba334d34757a9f39e2d99c97f00ca4163e2d8ce24746bf5619338c279c873ab2d28fd7156b556816ee7cc2833a12c4391f65dddfc8392a00', 'centos:aarch64:8' => '4e6929ad6e11cc9554fc4a4aba5fe336d01b4e4a4d99f12bedeb3ca0aee021e5d59ae05b0ed184ba1f58be8c9a3e48d9422d076e048a8ef3ab70a2080e00688d', 'centos:9' => 'b518006d054e123142c186b7eeff5f0d76ea3828487da6cab46e1ad172367a92dc038b1b88dd284226909df25bec3979e400637336839a4d322cac376afda8ec', 'centos:aarch64:9' => '60e416ad017664e90f8fcb6662d4e1f1d1aedc12278918ef5236ab5900926ea68d6f8f1a3ef46b6c1aba21483cbe9d9f1ab1332ddffe499ed0811cc12a947b4b', 'debian:10' => '16bdd1aa0feee95d529fa98bf2db5a5b3a834883ba4b890773d32fc3a7c5b04a9a5212d2b6d9d7aa5d9a0176a9e9002743d20515912381dcafbadc766f8d0a9d', 'debian:11' => 'b9adf5fde5078835cd7ba9f17cfc770849bf3e6255f9f6c6686dba85472b92ff5b75b69b9db8f6d8707fcb3c819af2089ef595973410124d822d62eb47381052', 'debian:aarch64:11' => '06a380b936d34265024031fe0daaf4aca88f9f6ad7a35f727ab68fb4eecb22d96287cfc0713d57bfcf0051524ab06477197594bba4f3b39e3d3030834be8b12c', 'debian:12' => '276deea5a2f071d1a9920fe1554233409dba97c7975d9f33f1425d567f5f6ccae1b102b27cddf538750260e459e42e41938c62164aa0a804c065edfa9e50c60d', 'debian:aarch64:12' => '9472ed980f2a2fe52ce93f31aa7b08b9b60a1650eefce042c0b73e7236c9f54eabd902ea6d94a9153feea6630b4d2dbbd49c11dc4d584ddbdaaa169ad33a4c52', 'ubuntu:16.04' => 'ad8aec36dedc00aeebea0b3a5e10a8cea06d94db5bdea37883d8040e6c5e17287c78413f1de37067164c919fe47c57d5907a3a2de7602ea047bf819425ce4f47', 'ubuntu:18.04' => 'bc29397fab79440a6677844dcdd550e6a157cf4c4e779c9aca5a9d38e2d8ab1e5527585e16bebf4deca765fe9afde2c1ebc1f677d2444b9b48e6bf9a2b97288d', 'ubuntu:20.04' => '81a076a04725e8ef7269bd5c381619b0b18ca866639f064088c80ee12ca2d5f8e26d913723fd5f3ca609aca4cd21540f51e67b16a5728fd2dffa151e8b61aa57', 'ubuntu:aarch64:20.04'=> '2bea405f717de94b105494c5a2cfbd37b3ef8e9ab6902ecbebbae2242fb1fca1fc255f68d967b2fb21ef66b358830ef4d6c30d2b510fcf0a1972a6618f75e806', 'ubuntu:22.04' => 'f714682aa3bb7a8a86880973eea1461baf2d00fd6cd8d67a5e0c287079bc36da0cd7b196501e4cad708665b713d7e97452b03e1c33db58dc3ed6a05c2893b32e', 'ubuntu:aarch64:22.04'=> '5a408486dbe7c4f84e7f4f528e0bcbf396dc4f256f78f8a6a1604e1837e08939e5d6d11d3aa440ae9623f6a3715898744791ca6f147f533f47fb115286b3398d', 'ubuntu:24.04' => 'ca2049c1c0137ae1b398580779976ef3fd99c50d464b1ce4d61d403493ee23f4016ccef3efd1ba72fe1e78a47034db694ce1d3df36c1b3084e15bf74cc71b4fc', 'ubuntu:aarch64:24.04'=> 'a5189e475e5a8e0c605a31c178e73b1bdc1a598e8de033f8f07dd13c3c63a4593e7920891917b72d5431045ce591746e7ca9d8a9c09e33f8a760dfc3fca61489', }, 'bpf_1_0_1' => { 'centos:7' => 'b6c6b072cef81b2462c280935852f085b7e09f9677723caf9bb5df08971886985446ba20a4aa984381c766ed0fc2d2b9cd2afaa7ab3d63becde566738058fd1d', 'centos:8' => '70787f1c28791e01ed1612df7bce226bd7e293f3b17ecfa22284841198a050c0422e46dc78597226c091e04198b8b0681cd1d4d5fde4f068434c45876b1dad72', 'centos:aarch64:8' => '2682ecd75542212b8e23d63cc057b9a9ee944cd991eb20a41ae25bbc61828de440cae5927a6f34e31182a642854b91709146781b7a68cf52ae9f952b0b358e38', 'centos:9' => '6313c8ad1c6e6e070e06796807770a291d85ae6e5fb67d3045dbcb144016968e34b5256f83006be0847fcc53222b931c300b11598d7bf031d75c6284343a6db1', 'centos:aarch64:9' => '373d469eb4c8281c1632c345681d5bccf58fa65c23620239df651ac669b8c2abdeb926e7304fea513881cab07eecb3815a161220e671ee5ac77b2aa36803b0a6', 'debian:10' => '8f2e456bcd0b89fafe97c6368f725d85230f183f72a720bb4f7da043ca6ea255a2c97e374eb78407e8ef969d84faebd63cf5b509eb9bf4476fac5b08567574b9', 'debian:11' => '11e91e87b2d10d5e73958c1944abb50e1c9df5891ad563683e2b313848c85d3fd15aff8ce70445afc768dd490c5d8de12f887b314eb55af7e4a8f9613c003806', 'debian:aarch64:11' => 'a187bea689bbccca48ca5cbc0fda015251713db55215e6bbeade1e96f93b6391597928dda66ff91cceee254dcea7fabaa42c05336a900260bd23503b0f385f2d', 'debian:12' => 'c1023d1208a6a8a43afa143df13bf3e83384c56270ce1869d5f37afe8445edba239792c76ea9b731c24cd622089ae89cd053a499ac3d57983902295e2b963985', 'debian:aarch64:12' => '546fd47f77992ca4c63b27e37e1143a6d1b6d532ee39d9bfa2db9814eba786626eecca8b6439e2ada0041b82817f9b0a88ef5c87576d3d19519d1f69771b28d1', 'ubuntu:16.04' => '548a80ace21320b6ecd170868852dfa1ade66c23a5c8fb4879ca911132a2ef3bcce51e622786e69ee0b60ddd883a15e1fbf65b6b03d36c27219dc20e824a8eb7', 'ubuntu:18.04' => 'bac4d836afc9b24d3939951f8a48a3077a1ebfe1a3523a4080fa07cda6a2bc5b48334452b4f9b120ecd17936203820c093f8ed3e900d17e23675ac686241f823', 'ubuntu:20.04' => '823cf47a6af9473ab640335a80da075be1db40fd27e3aaef84fffd36280c1b7982640795d09e541599473177e203d6cc21615d87d8f0e2b556ca8268cc3ca4d6', 'ubuntu:aarch64:20.04'=> 'c7947b81d4894ed47d73f10ace9cc08cad0c668995151d6e95cf93f8a7ffb9e940e77c65deb423902b8ead7e7aa3144d14bf7230068ba34c87b27dd7c7e76bef', 'ubuntu:22.04' => 'c92b722e28624633de4e8411f89984434d4281d4e674677b800317235f5363285c5618e93a59945b90196026b0c4d061716c7e5bde817e4f4bb93efbaaeefb12', 'ubuntu:aarch64:22.04'=> '5899eb3741c29a7c7d1e6f8a60fa08813890a60b45e5f7d0f980e74287ad48953d897263b8d3455116ac058db26e424576e1046f9c35d0c9262cd31b0ff0e4ae', 'ubuntu:24.04' => '78598ddb3901c4ea4d305996b54c5ab5d1b7db694407abcbc390cb228e31dfccd0646d1354ef374f7264f0a9ccb00c0a8ce237c32934affbf44c59b0033c564e', 'ubuntu:aarch64:24.04'=> '82cf3e15de24cb4d49c9c8cc3cbf1ae325e2ef36d6246018036c5ec424f64c3a62c8c7d52bf6e3b0aef060721f0d641f80cab7c5e654f48cbfe12728ef03c728', }, 'rdkafka_1_7_0' => { 'centos:7' => '40e01992e97b4620eb6c86def5efb69734534cf24a374f74ba9a1f640303f08a413bf5c3d2bed447bd1afee082b0bf213d4d3da9dfe696ecf3226a2058098725', 'centos:8' => '6a733ea0a86c042071cc524a6810483ccafa9a254a1f091288e862f239e95f835c727c190f5d6a9098bf0a9253c979e14012a05cfacd3df0197d1c99c232dcd2', 'centos:aarch64:8' => '2dfc6ea5cca47649af47a781546ba2113bce5d6025efc9f6254a2970dfbef0aced3fb76bd53ac5f5dc1ba5fe47a40af9fdd25d8bf8ad5eec95c099b27d384921', 'centos:9' => '544d070e5e61e8d12bab9be9c39d040d7380308241f81f0676ddb627764066788b4a4a45693b8d656d2ed2e412501cedb797b94cea1169d18045ac541d51bf82', 'centos:aarch64:9' => '8c3e3abd7428359fb3085b62b9f4f8683df2bd486ca67095f2e05ef474522cd8a87fbbe7e5b448b46b197a80f4c0b611fc346ae386b934e1b3597263f92cb793', 'debian:10' => '6abed5425d55ebe0c251e9b4e51750297491d7fe15c4e1584c53f38d660d0ae91cf05565c9db5cfa36d217becf32ed64a099b7af55391da299536689083cf6d4', 'debian:11' => '2f94bfa0602b780a4f6685d4f1b56ae1276f9e6688bdab380b6e91b90f9f83330f94eccc84d95171399f6c980c2eefd72a587f0d483ecd159cbe9d23bd9084b0', 'debian:aarch64:11' => 'f46ef8109d0d606e43b862b82dab27c623490cb12182a0b67749b267a814e6febb36ae54f9fdf352e5041d4082f013ec1c556fbac023fc5f943462b2332d3a73', 'debian:12' => 'b7f12a4b78e974e078579e2e2d52a05a82bc1457bfeadf4617343a01cf6a5e0e8e5bf3c12114d4554407228eab3f1fb494843b0b438e6293b03df99bb1c409b1', 'debian:aarch64:12' => 'c19127218e44c903e7059ef7d78204244ad05fa76930e3a9d22223750e361198b6fbb05f3b6a992a16b4b225620a2035cddd8cef50c1c926b564d8f4d9fd3edd', 'ubuntu:16.04' => 'cf2610a5064f3149398c6381b595f746dc489da249376db66658dc48d769b0a4f0526edc70130736899db213663b20073705f121c773b92e6bc0a79f46b701cb', 'ubuntu:18.04' => '5c39654d637d8badd9cf3a0c60d2e84dbf84378d3ef57e20db8f86f018759fdacf256a886fe1fc05e8fa34bb4495593c3e80c2cd8f79933c7e653e60c5a03dfe', 'ubuntu:20.04' => 'cdb12d94f906322be7bdd2ea91b54c375656d0ae8b617be11c236e5f195fa08f3324596a8a85bd09736192b795b05b626b1228a51c82279c29d330705d0a25bb', 'ubuntu:aarch64:20.04'=> '09c948a852cff381738106746c4fd0951a3def933a32480a2289439e8afa582b3afd273baa69f7e0555acc0ab3d428d9425a44207322a67ac5215651d03d5c0a', 'ubuntu:22.04' => 'c547ecc1e5d94f557184fe7a9202a56065edc5afcb71d8b803be7d864c20422af889a7ed693623280ee0a0991cabde99901edfa5ed02a352d9bd63f716277f42', 'ubuntu:aarch64:22.04'=> 'c51ac309888e879d303f4aefaaeba1d86c1e2159a77dc9f0c8e8e0638c1a80f133f32b197cd4aca7e7ca49142ad7e96d964542444de94817eeb2bb608c8e348c', 'ubuntu:24.04' => '17b7aee8a36cbe12029dbe9cbb19857a77147f89f294b3e730cdf0ed2cadf0dc1f34d98e2404b48f130f83d632529bcbf85b8479ed774425cf18f72e1400a3eb', 'ubuntu:aarch64:24.04'=> '4e518fbb0594447250b7242ab3210c191ef009e2d8f22ec70335d84fdd37b86b07802b20bfdc1e5f6caa89e90b0704b2c35ded3768385a71296b249355c1a92f', }, 'clickhouse_2_3_0' => { 'centos:7' => 'd265d32ecb417f881c8cef21e6c3d4849105565e4b912feddcbcccf78676caa8afbfccfbb48e3783510ffccdb41cdc76b117d0b953d5455b9cd0aecd1c3b7b0b', 'centos:8' => 'fafe50590a51daa7f43cf03b073e718ac36368e13699c5848d9c19ad8eb0d0eea6f2aa2b52490aeee039ea8b991dbfe4a3a37a9f3965dacae241a692f63ee096', 'centos:aarch64:8' => 'f97002fb6e744b06308bcfcf2979b039a08c929902726388785fa9a90a77d6b09d8d4ed3241362a6ec18b2ed747dfd8e0576b3fa195e3cac253c0a43f342a0ef', 'centos:9' => '189523411392a0ef076e15c7bd3a30a99572f52682f4b51533b3da47ac666fccffb970f1cabfda0ad8268fd23a2f7909f2c48d3e769daf384122c27f8f75b507', 'centos:aarch64:9' => 'dd09d6d6fbe35cc426cbf496a8147002231bf1e74729eb3b5a2dd1fb16c5e7ac1fff5bac61dba62682a9df0de982b35f8ff30640e74e591f3a4c13124890dd79', 'debian:10' => '65b790625e8c9214bd51931cb7016edbf7af718b18eed78bb218386bbc1031c1f7ebdfbbcfc6dd333dca64b12e2c57710c94be45f2ac2954be563cddc2394ea0', 'debian:11' => '5685f91cc970af9170b5699edea679a567c6238f355265de20e9d83a514422d85c13128b29682b915eaf41ba5bd7116bd927eec20bb88d0256f2bbf078e5601d', 'debian:aarch64:11' => 'a19c456d98fae1e25a12b6156ffd6750f2943d5323edac51ea971edc8d9639d47e934a1914bf5c6f72ba7e90c7a6d9ce0bef361b3a532df39163e7096dbe0d10', 'debian:12' => '675d84f18fb77bb3c63cb138091d3132f0cb16c7e09ffd42a101231d285bc0237fba4c7fb1f8b7ed2fed97ef4a0085f812a1191bbcc7c313e643719941d7fd41', 'debian:aarch64:12' => 'cba719df6a0a3c4c19413854c409c11ea5b65d66fc3acc167a3086f7dad0b8a0ff7c9d3d92a590d94bb89b7050b73ed7e7f5683c42aa1efa8d250b4b36ecd12d', 'ubuntu:16.04' => 'f0a19a15c312eaed2279831d8688ed748f8a82f263f63556356c1ced4e80f9c9c19ab9eecfff503427cc1e06c406248407e3edd60e5cbaa3945782cd53414fd7', 'ubuntu:18.04' => '45287e7ecc8b55fb0abf6eecdfb7b75c7a07ca564de2ac85b0b33b5883f6eeabe4c260a13a9b69fb89f3a64350e40a1745a10eab8d68b593ec9a89240a9856bf', 'ubuntu:20.04' => 'db2b7a5cc042f02002ded685ca201bb025a293be9cd619c4d7ecc51c04dd49abcd2b3345c2905df515e94b3e6441a72b68fedb9de445f18f2b48482a838ae910', 'ubuntu:aarch64:20.04' => 'df2bd0c9255a56ee5111287ddf6f6ee06b233b10df5fcfebe44f3a8b99767c23dcd7dfb23dc69e28a2bdcbc5e9b6a320acb886e7b36a48333f86fef625fdccec', 'ubuntu:22.04' => '721f0de7d4e8c07ad44313bd33ad586fdcb71d367e8d6eb9a398c945ae258724b182eb00075e62940e400b47ec5409ddbffa720f01b391af353056c20900445a', 'ubuntu:aarch64:22.04' => '091df73ed7194bf4075344c9a65c023680a53ee4fb11cbd6d615309256bb7df3cdea1e34b3ac6a36021646a0288fd8a9dea908079394767a0be0c8588daf3d8c', 'ubuntu:24.04' => '9fbb45a10e524b01bf0d659258491cde5538d3a300c7da06e6267510f56d0cb7d7d1f38f359a371bbd580e7fff6c43f3af4dbd6748d6e91424931f95d73bdf26', 'ubuntu:aarch64:24.04'=> 'd894452fdbc81850f4fe242c783bf66b1a8e00ac12df193190714bb32349d3b373645e67988caf59051f08b47e85328af347c35a9b09f12e4cffbc3ddd1ba62a', }, 'cppkafka_0_3_1' => { 'centos:7' => '47fc81102062f418a0895f2787bf337da8d7e766b178ed315284cc12913c58b99e395ad044e2e5954299c3c9cde23b3145dc43d4469d360aee66cc850b09b82e', 'centos:8' => '206b85a820f9c7f7cf4d9c2da600df0a7997706f25b356d355121136fac524eeecc2ff3bc625f796693a47af8ccbc4fc7cb8f3d23f656ee70c7ccf57e8308922', 'centos:aarch64:8' => '5f2eb5af9579d4bbd6439f9359e9139a2d33ada050b7aa12e01a187594cf07c58da20c969cf53a594a58a6dcfe333644439e60f138861905546bce81a4f87f4e', 'centos:9' => '1b22492da81139d4f42640b43c43f2e3ca43dc51b6cb2136a80297ba20ecd28f0cfcc035f11526d1b42975558547f978bba01b418c5d9bc0d3f1e910bde5a8ca', 'centos:aarch64:9' => '236a613dae16f5ecd48144fe0fb5265ffa9b3d9b53edf944c60a6b0ebfc16f7318d17794a3a39f8ede4b2da4a82ae8e9627ab667967d682d8057076e6526fef2', 'debian:10' => 'c6316b9171b31eb94b2c31b9532d38f7db372ede62c06372b473149f3e4a2ba386439f4e5caaa7582f0ea015460903d4cd3f750b0f57fa5fc25612a50c10b501', 'debian:11' => 'ff83197e4d9056e629fe8633cb605ae8d5957b585f450f190ea01644ff850fa4bf69bccef15d15848896a35a49b3c38918261abe1bb6fc28991369666087ba38', 'debian:aarch64:11' => 'fd964a064e88d4f2038539eae8f419095744e9764c22fb0c9722d2b358116a261e5f6ccd277a2fd377c05b8a7dad727635aa972502b9c84fb7d887d9923fde5f', 'debian:12' => 'eeda4bc9d1a9ca87ddad988fba0999a66c473df7a0eb48c1f18cbdd3995a957d543d68c20b1ff8bfb8deac60dcbb4c47a6a71834f7131abb853ba6bb4c201eb3', 'debian:aarch64:12' => 'b6f5ee512a64f4fb7736f3657ffba8e5d36f304cceb8e4546ecc9202f1144a6f6db3833a841690d88ef60f1bb574bd91ac7db984f6f34a7097b5108857294e36', 'ubuntu:16.04' => '5025577664c889efb047235c05a38f083bd066da7a2d8261ea2357ff9b677b813e5cf11de7ed1f46e9df3fd846c54d977880f1375b44a6bd5b74990c34b36f51', 'ubuntu:18.04' => '216b21370fbf246c8b2b5f99c31b4d09935339e5d4e2cf529621394df1fbf5cf99cf859f2c29813e05c6f0e9c29c1c154d98676cc125e010ca8b45f3941b1337', 'ubuntu:20.04' => '8394ab12a08626c886e5d3aee676e2abc46aa408def8405759e840d2ed8fe7e6fd215ed79dd6e57c42213f3cf199f58cbbd8752286e3449a2e17e78865719278', 'ubuntu:aarch64:20.04'=> '2962a2fe17515ed254db3bb616d2255f568b9678931ede0c121245bf126ec97082bbd793207ea15ccf34e1c56bd350209ba68a0940ab8e526c435fb979ce79af', 'ubuntu:22.04' => '9da7b3c41adc768bbfa84d82f6edca9e5c37423c9c04766e992d0473ed5bc08817be01c35dddde33ebcfc9032bfd2ea69657b259161649c23ed64ea22ce95291', 'ubuntu:aarch64:22.04'=> 'fda755c2fcfe73cd09c5cdb1666b7d1a33562cf920ef8b87f70e693de8b0c86b8705b9bd9313bcde4122126feda65c5d15e25db43e68f5a23de9b628025035d0', 'ubuntu:24.04' => '38bc724834abfa5fe25030bd913be0263883035404cbb69e7f7874b42c4d5d26b4efcb38a82eddcfe6012ce6c8e07479dc9a8ca8224bd2d7b71c6efdf14732b8', 'ubuntu:aarch64:24.04'=> '3403236656f3fb402e79bf8895a8427a42947dc3a1610073f3dcfa0db1f1b8144197fe1fa9b12f0dc5c12cb97a6da2cef2179561ca7a904f917aec6d69a37714', }, 'gobgp_3_12_0' => { 'centos:7' => 'ee4e8a976a16b4f0e49753a81e17405b3c219d979313a39e059ec99125e7a5804691c30f2989e60b404d6a9cdc5636d6d9e545e6766c3fd4bede4ff0b830f204', 'centos:8' => 'ca142c7a76f17e4f69ef7c14df0751425656faff8ae1309895ff295950e6fc82ecc497e060f437a45e3e8689f0c561d102f5cb8f920928d2b3ecc91430011a22', 'centos:aarch64:8' => 'f701bbf7739299706799afe0f8dcf1cf9f6c6bbb2046f445b4c7b3498dacfaa3de456e7f960c3a2b07580005dc78f1b273e0c0595e8ec1c37a3f96301cb71acb', 'centos:9' => '25c504c671be26066c1f370eb03bb681209c87bba0c6fce766c8416534007af8e8279b360072711326fa1d248255201adf06c0ccbd1682ab34860d21f72c190f', 'centos:aarch64:9' => 'cdd4c9a1a0e25f584a1f33ba159c34d7a8b7d3c285a75a2ad0675aebe94aba888726463eafb37cb4f96751be4a474e4b95603c15c7bef0219586bcc94767263a', 'debian:10' => '10a38076bebd3a1eb6dd81c5374f209f17808e51ac6476dc256d2a91cd904d5b15a6f9a2161099b2d1e6eaaecc1c3beb54cca1083ac11900001374f4624b38f3', 'debian:11' => '750102bac73ec2c1fb36f470c86d37a2ae59bd49cf76e1ea48385099d6a3bad0bc49768ee3b41ba2f85de7131e28f757d6093c6be879165e211615d50d69239a', 'debian:aarch64:11' => '5569b4bac0ccfd3a372475fc03d1a4ea6eda37b0c28b8e0854b831115396b4d5618c6ccca50271691f800ece304983c2e8022e9e507f1fa0dad568d320431c61', 'debian:12' => '0aabe505b4ea6649043203a9f4d28dc27efb6fdef8c61bd0c21f0696dabba697a8f7568ce8455a90fb50dde87a30572e8760c66a5d90e647bc0facbe7d9ed2fa', 'debian:aarch64:12' => '6a857eb5136626ae2ebb7c5bfb5204172328e9a841a193b0c1f76a87a72472c0f8062f386af49ab3acd59ccf7735e0b247489e15af498dd734bcd66963509d3d', 'ubuntu:16.04' => 'ca142c7a76f17e4f69ef7c14df0751425656faff8ae1309895ff295950e6fc82ecc497e060f437a45e3e8689f0c561d102f5cb8f920928d2b3ecc91430011a22', 'ubuntu:18.04' => '55a829e69aebe296ea1537117150982003f0a647674bfb5b4eafd298db9dc9ea6aa961642693d5e21ca737e735167cfe3ca84780144e842a9946134a99c1f4f3', 'ubuntu:20.04' => '750102bac73ec2c1fb36f470c86d37a2ae59bd49cf76e1ea48385099d6a3bad0bc49768ee3b41ba2f85de7131e28f757d6093c6be879165e211615d50d69239a', 'ubuntu:aarch64:20.04'=> '59dbd865794eae5302b908d82c5b89c3dbbd95a9fc47b78bf5e16fd1f9fe34aab03d4294d90ef3000b2aa24039bcc1c939da6c5223ef6f8011370f2024a5d22f', 'ubuntu:22.04' => 'b3f2168c6b705804155ca944a6c7850057d50652efc57c3c4194838b147176779b98d7b2bf9af72ee141805d62f7a6c1460244ce60b570dbdffc114d5e7dc899', 'ubuntu:aarch64:22.04'=> '4e9114ed4c9fde74517f04fc8b79b44854ee5fc8b51b99816ad60dccbc61a682993c4321a0df6920a40164281ba24d171f9d4972e54a9f3015d993caf7d03c47', 'ubuntu:24.04' => '9fd084181abef9cdded2b95a1d0ff95cc0077861fcd6dc83ef47d92e0f0f50cda44afd03d7fa55838562418180e9362725757c0d7ad8f9f09b3b4b2e27701514', 'ubuntu:aarch64:24.04'=> '44b4b83e3ef2e8d6ceaee6e433edab7acbb3396f35561598db3c78d5dbdae05031158b50364e9d7d8e6e850a36d4f2506456cd62f480706e44063f2751549c17', }, # It's actually 1_1_4rc3 but we use only minor and major numbers 'log4cpp_1_1_4' => { 'centos:7' => 'daa469b116ecf20004163b5e222840e10bf1c52e92dd28eff6839d3366e260ee63ddddea1e3ce95034243968e52b28c879e508fd05536746010d834b63cf9346', 'centos:8' => '8c46f8c02835732ded30b075dc90de0ccbd1fb7ee9b68788791f41638c0ec5fca80ed10ad8f447db7cbcf3a64e9d04bd2178a2a33319909e3a115862fbd63a91', 'centos:aarch64:8' => 'ac080e1a74f46063d780d4355445851d41604ffc43cbc275cc0194fc327989d2cbd8e8888156768f46016ab898f72bce27cd29bd53b8350b49e72048f97b6dde', 'centos:9' => '010c54e0b62aeda78509bb404e372e85e42073b08b2d0ba90f7e7fcd8bb3d6b943acb56e9dcd8113b44bc6b3bbaa83572cbdf81a9dd7e5b555c29fc73ff46ebf', 'centos:aarch64:9' => 'f7d2fad8ac0bb8b7890e9c21fe4b178ee7cefd252484546c91f2c8d21eac6673addfd59323c2623abc1a5d0bb641b807f42153c782a84f1bc593547b9509515b', 'debian:10' => '5567b2a040d71e5c1c44687dd9ed32d3987590e0aae3b5653add8b7885b9e1688ecdbecd23d5c54a309388fcaf234df24464550f662a3f76b3a8a235d93219e9', 'debian:11' => 'efd6883dadd7ffc01464d22fc2380edf8b0356ee897a3a3cb901008ef477f9e7769f63bcf66910b3db680dd2f58cb554228454d7c41c30f8782aabc396625d3c', 'debian:aarch64:11' => 'e8be2f9eb59364726062047eaaa869ee5e2d0fedf29e1039916360e9f582a9bc82fe3e6b21f5087ea20531ea5b3d79e26a901442fb85cc1c49a5c52d20805bf4', 'debian:12' => '4411c5d9790806e9b84b002d1cb0115629fba1cb2974ec673e94faeb6121c336c7598961744ca2c15ac4b230e9ba3b5917090e48382d4d89cf9c457a72891867', 'debian:aarch64:12' => '88f8d935be3a4141d331305621accab0314f09e1b285bd7af3a2ef1a16646e2733716803600d52c042ca2c458a0531b69dd592bd04d2bbfcac5111fdfbec9fef', 'ubuntu:16.04' => 'a13743d266a110f3bda44f36c80b62e92c3bf0408b5384488c61a0d42ad2e62212eb39906b33b872afdc567e72ed7b1cc94604dd6dd2c91841a73e6967d6d13e', 'ubuntu:18.04' => '6d5a137f839aefaaacff43301d35351b74417e2d45dfdde705c2fc6433aa8bce939be7b727e8b02f83eb19bd4b53b0482630c9035af15856c0738335041b7e0f', 'ubuntu:20.04' => 'c2177531121f08279e417489f01b6867c394e4f65d27a6e3928e7377b48adfbbb50bed28eaa17afc230752720d09cb9261df25d9756aac4919923e9599f10e0c', 'ubuntu:aarch64:20.04'=> '04e2d04e213fca3002b3b943af76a3d32fb150508ec06030843ffd64ec11617c18228f774cf1e9c60bfc9d5d449ec0ef8546abb86154008970ff1ec1fb9fedad', 'ubuntu:22.04' => '086e182a2ca2475d46e4a959398d52434d5ad700f2db5a568a5c77b6e91bc51fc34cd36aca7be68810ff0981d304d444bcbb5bc2fa549f8ae3f6d24d8a91157c', 'ubuntu:aarch64:22.04'=> '82b6229a5346d3859f3263c5a3fae403f7123b2db805e7ef8ca94d3222a5aa8d8ce79a2f6e3345a2849bb949aeb984010154e2119dc20adb5e7052271439492d', 'ubuntu:24.04' => 'fc63e000d80d0484a9bfe02336545adca368a7dcdee8f2087440a9826f34df1bd75de982247a1727bc6f0d78b9f4c24efae0d61e5172e22c4c9a20601990412f', 'ubuntu:aarch64:24.04'=> 'be8d01ee93efaf64242853d465a993a5bdba191c832a82fbfc0179b7439ea391965e0a3f464d0462376bbd94a946eceb1928bebd439a363a69b93609ce567882', }, 'gtest_1_13_0' => { 'debian:10' => 'f8759bd2a908e533f56b8401464189f75938ed8ccd23b5de36f5dc5ec408d7c790cef1d594bbeb2e52b8fc526f8267fea296b7e6d159dc8f2b5c4aa7618daf5a', 'debian:11' => 'ff6e7e6c6922a821173e8ce74340488877c39b9fac35d2f0d6137ed80cbfc423669ff12dd55427553011b700b3a48c970543198748d8fd145d8aa11b62b250ce', 'debian:aarch64:11' => 'c30f0962f2ad957b51301ac4132e1b5a2460415bd79440159775a28cf6d0c6fc9a70bef335783ca4bfe3f6ca1c2b60d9d4b0b5be978e5e797127d3dcf91a9a3b', 'debian:12' => '84825a13b6ee3523e7197f71cc45e709cecdafe02be7fcec1e54cc5551023225603c48e1cddc7c3bd3984efbd6c99559f40310732f8390b79f72973e5a5861a2', 'debian:aarch64:12' => 'd840ea5cbcca6cd238b133df17457de2f8d6b21e3903c259b1cdf65cc84cd86fe9a8c062c1b09c43b0fbfb377f8f64ba0220f9740e626ce3b03a16a026ce9ec6', 'ubuntu:16.04' => '25ea67115d12bb5a647cc762c4c84e63736f0987fe9f07cf84664127a31f93ffe782bec6dd1b9eeffdae27aa13fe2650af6766797baf2d51c8ec2f445cbb637a', 'ubuntu:18.04' => 'eccc30f8817656ee5baac9d7ea5502c0461aa2958f61c662b4e55d10f322a0162ef2ed2a0190478ae51650b345714636f612de2fb6a2c243ef8e2a372782958c', 'ubuntu:20.04' => 'dd2dae6998f75b88c7b0318737dd9fc1a6f7d59834f0339100a45592cf1fe3056c86e30dcf349a12ca0ec1b0f23fb36d26c856d5ff7b7bd624f3dcf36ff00bfc', 'ubuntu:aarch64:20.04'=> 'ed97bd06868835ba3e389cd9ca0ccab17df3f5099aab7f8a37eefeed03f65c5468be5cbb4489fe790ee95968a84200fb1c51b5d0225396e2ab27e7372bfb0356', 'ubuntu:22.04' => 'adbf6361f608a5dc08f9daef9575b8fedbec8808a3e82c6e8aee4f51325209b067a2e471ad3476da36c4ebefecb5b24457f005626ab48e569f3c2239a509acfb', 'ubuntu:aarch64:22.04'=> '76e22f295aabbe2d735fcaf753c7648973fc149dc87a3db029728e6a769a4ad82a6c92783e213efffe27ed1f4939b2098bb359ba366b76fa1e994bb80dc60d36', 'centos:7' => 'de48a8b8a7403f95bac0abb18d166a48f9c1e60f2b899f1153f177eb89372b8d05ed336856a93abaf6a891b9d8b374470555d04ca3bada7b7032e44d05628c2a', 'centos:8' => '85f4f5529d72f66722658fd7e8240857b3439ebf9066eb87dab612400e9e9b7d3209e56db7cfa5d3ac64f28e75ba19bbacbf9f433e561073d41a22f40ce65b87', 'centos:aarch64:8' => 'f4b5873c602495ad85ab16ba531ea19c3ca925d5cf9ee62b5a2eccc0b6d85523b0645b04717e1073ca92bca6de91c5cede1d96b30a2911f3c579755ea1994a77', 'centos:9' => '398948e08847b7ac09a232414dc462abe9c66d098e71de3350fcb9cca38fdf0204a302d7fc75ada4fad33072371059646c4d2167baed311143240de9a8e62091', 'centos:aarch64:9' => 'c441a2450669fb06cc2a237a1098ab1976a49b521069e021fc81de0c34f4de97f954ac3449120f3f80963ce72a7594a449b7cd94ba89b7645397a7a8df469124', 'ubuntu:24.04' => 'ea6129cda9182bf0f4ace130c579af07f2cd74929b4598dedd5febeae0fd0b6e1470eb256168c71fe4efaab6d750e4d52067fda132cef0cb48f1767f123531c2', 'ubuntu:aarch64:24.04'=> 'fe86cbd52ea4f2e8bb513dd608194dd4ee7da513a730185e7d1d51d97b5c87b1ac4b413a7330066b44fbcab510e36e6c0cc30a0ed2f085f6801d3512806ce632', }, 'pcap_1_10_4' => { 'ubuntu:24.04' => 'a6b09826c7b180a84ab5eb6add7e433fd37772610d7f31634e3637f7c6397232bbf558bbcf8d63aa1cab5357297d996dcdfe2da04179c705649156633c0263b5', 'ubuntu:aarch64:24.04'=> 'b65d7aeb1a7f4189d3cf047545270f688c2b9f6bca8a62aababd68121e0e8369efa283bc1290486f188d8f225b1f7c2bf741fd27f2848bae938efbdfac72855d', 'centos:7' => '24f30e6d52cf7a76c059734a8feb5e8e8123584cf5bc0be0097a15e3035b984b7c7c74244115b6799dad36a074c69e4aa325113d3d12912ce9719488639e8d36', 'centos:8' => 'd83e92009cf5f3144b0dccd95e5acd90d7620d3c68f6ce58b5f3b5b71eea7711de986d61b5a9c2544fcae6f4df8259150f031d5c08e9b312b2eed3c6816364f6', 'centos:aarch64:8' => '4e8d0d85021c4dabb62100428ffcb7ea37d9b9e4088486c319dd1692f169b0383be7b9fbe8a7ac7f9bcf1088b5944e211a595afb0b72f07d7f9b630ed2312ff8', 'centos:9' => '83e72418b59f5665062674d3ddb1c6dda2a9c022c7bf3a2dae700ff221f7dc000b22c5b775a06c2a1230d57c93decd9e2ce7482a50f9e62f99063adc201727e8', 'centos:aarch64:9' => '0342437fe2d0158fcf6e43fa3acba86db3d67c24aac6c6fcf5b18b766a42344c81c516d2f07d8a8619c37a3801216a508ec9db2620f111605650fc7cbe5795d2', 'debian:10' => '0af93960a7bf5eecb30cc133b2130d2cdf2f7a455eaa939fdf526051eaf8418a232ac48a0f31f9a6b517ae8c6c091358795afa4237dcfb2d5949cef567713f4a', 'debian:11' => 'e19cf14f8e9d3035d9a130286aa8b50e6d9254151f22343d56f64041d5b8f184cdddbc63d16f40724e6348e1aa06dfa721ec40bba3255e875ebd8c9d69fa0464', 'debian:aarch64:11' => '02214b7eae112e3a513019ba7841664d6d5b24b39a706f3176a9a8f0a9852b74d961ae64fb85979521f1016c1a14f4ee104edbf40ffc664018e01a0efd8c540b', 'debian:12' => '2a78c9c3fec589be3a72c672a70b2ec980fca490a89724cbf593ee71779e94b5f5f40ad4b15bc0364e659f324f5576dcb8e629dcbb1833fbdb045c6bd65ce90b', 'debian:aarch64:12' => '008d6ebf8428097f2a06ffbafb72cd0175765cff97dbc10e6b17eb21e7734d5dd56cbb834090a32f2bf94858668aa1f9512d9a208a92a1f3fea08f82480af088', 'ubuntu:16.04' => '7bcdfe05c80157e38f06e0ae1fb21ef3612594359b05a1690268d7d71bb93c8c3e4d3a19f18c31becdcf7e77828dfebaa7d251f017e72e794c94ec268a100e4f', 'ubuntu:18.04' => 'b1739570df8d97212b39ad7e98a8df7f27e42132dd1009969641c0c9bd18342b4516952b8fb116ef371ab0b1f736f0c2de3ec207776e1a09c9ea94b44d0bbdc3', 'ubuntu:20.04' => 'ee5120c695ca1f8f3db7d546c77f150c1e7e7734dfdf4975b0c2e8e5d33ffeac0260e1161cf4a354f92c0ab8e3d4bdb74e9c85f94c28a0259f5d8edd4d6dca3c', 'ubuntu:aarch64:20.04'=> 'f1b43b581968b4cb9368eefe4b41f98678ad05b12623a1ad0ea8a03753094d8b5eecc8b9fe585dfd2f3b4ef5d7c4afc7c2e13e68a14d5b06cead53db478a7c86', 'ubuntu:22.04' => '6b941f3ea3f0131257adc6405cf4a9b81239ebc8cd005406bf3407e2dc063cfd427d0c8b24af8b212384e6e6adc44aa9f85c967af3e8af807e6b3c65b4c469ad', 'ubuntu:aarch64:22.04'=> '4a43f189d246b9d83f2470980ca83459e1f5b728ef29a3960e5ca3a3ada3258afcb5834bedbd4f4f909db7762049947c37521b1355cc770b12ae7adbf919ee09', } }; # How many seconds we needed to download all dependencies # We need it to investigate impact on whole build process duration my $dependencies_download_time = 0; for my $package (@required_packages) { print "Install package $package\n"; my $package_install_start_time = time(); # We need to get package name from our folder name # We use regular expression which matches first part of folder name before we observe any numeric digits after _ (XXX_12345) # Name may be multi word like: aaa_bbb_123 my ($function_name) = $package =~ m/^(.*?)_\d/; # Check that package is not installed my $package_install_path = "$library_install_folder/$package"; if (-e $package_install_path) { warn "$package is installed, skip build\n"; next; } # This check just validates that entry for package exists in $binary_build_hashes # But it does not validate that anything in that entry is populated # When add new package you just need to add it as empty hash first # And then populate with hashes my $binary_hash = $binary_build_hashes->{$package}; unless ($binary_hash) { die "Binary hash does not exist for $package, please create at least empty hash structure for it in binary_build_hashes\n"; } my $cache_download_start_time = time(); # Try to retrieve it from S3 bucket my $get_from_cache = Fastnetmon::get_library_binary_build_from_google_storage($package, $binary_hash); my $cache_download_duration = time() - $cache_download_start_time; $dependencies_download_time += $cache_download_duration; if ($get_from_cache == 1) { print "Got $package from cache\n"; next; } # In case of any issues with hashes we must break build procedure to raise attention if ($get_from_cache == 2) { die "Detected hash issues for package $package, stop build process, it may be sign of data tampering, manual checking is needed\n"; } # We can reach this step only if file did not exist previously print "Cannot get package $package from cache, starting build procedure\n"; # We provide full package name i.e. package_1_2_3 as second argument as we will use it as name for installation folder my $install_res = Fastnetmon::install_package_by_name($function_name, $package); unless ($install_res) { die "Cannot install package $package using handler $function_name: $install_res\n"; } # We successfully built it, let's upload it to cache my $elapse = time() - $package_install_start_time; my $build_time_minutes = sprintf("%.2f", $elapse / 60); # Build only long time if ($build_time_minutes > 1) { print "Package build time: " . int($build_time_minutes) . " Minutes\n"; } # Upload successfully built package to S3 my $upload_binary_res = Fastnetmon::upload_binary_build_to_google_storage($package); # We can ignore upload failures as they're not critical if (!$upload_binary_res) { warn "Cannot upload dependency to cache\n"; next; } print "\n\n"; } my $install_time = time() - $start_time; my $pretty_install_time_in_minutes = sprintf("%.2f", $install_time / 60); print "We have installed all dependencies in $pretty_install_time_in_minutes minutes\n"; my $cache_download_time_in_minutes = sprintf("%.2f", $dependencies_download_time / 60); print "We have downloaded all cached dependencies in $cache_download_time_in_minutes minutes\n"; } upstream-fastnetmon/src/scripts/notify_with_slack.sh0000775000175000017500000000414315060514305021216 0ustar meme#!/usr/bin/env bash # # # # Instructions: # # Copy this script to /usr/local/bin/ # Edit /etc/fastnetmon.conf and set: # notify_script_path = /usr/local/bin/notify_with_slack.sh # # Add your email address to email_notify. # # Add your Slack incoming webhook to slack_url. # slack_url="https://hooks.slack.com/services/TXXXXXXXX/BXXXXXXXXX/LXXXXXXXXX" # # Notes: # hostname lookup requires the dig command. # Debian: apt-get install dnsutils # Redhat: yum install bind-utils # # For ban action we will receive attack details to stdin # Please do not remove the following command because # FastNetMon will crash in this case (it expect read of data from script side). # if [ "$4" = "ban" ]; then fastnetmon_output=$(/dev/null 2>&1"); if ($download_file_return_code != 0) { my $real_exit_code = $download_file_return_code >> 8; print "Cannot download dependency file from Google Storage. Exit code: $real_exit_code\n"; return 0; } # print "Start sha-512 calculation\n"; # Calculate hash of downloaded file my $sha512 = get_sha_512_sum("/tmp/$dependency_archive_name"); unless ($sha512) { warn "Cannot calculate SHA512 for file from S3\n"; return 2; } # print "Calculated sha-512 for $dependency_name $sha512\n"; # Hashes for all distros my $data_hashes = shift; my $key_name = "$distro_type:$distro_version"; # We use another structure of hash for ARM if ($machine_architecture eq 'aarch64') { $key_name = "$distro_type:$machine_architecture:$distro_version"; } my $current_build_hash = $data_hashes->{ $key_name }; # Hash must exist for all our existing dependencies unless ($current_build_hash) { warn "Cannot get $dependency_name hash for Distro $distro_type $distro_version architecture $machine_architecture, please add it to build configuration. Retrieved file with hash: $sha512"; return 2; } if ($sha512 ne $current_build_hash) { warn "Hash mismatch. Expected: $current_build_hash got: $sha512. It may be sign of data tampering, please validate data source\n"; return 2; } # print "Successfully validated sha-512 signatures\n"; system("mkdir -p $library_install_folder"); my $unpack_res = system("tar --use-compress-program=pigz -xf /tmp/$dependency_archive_name -C $library_install_folder"); if ($unpack_res != 0) { print "Cannot unpack file\n"; return 0; } return 1; } # Uploads binary build to Google sub upload_binary_build_to_google_storage { my $dependency_name = shift; my $dependency_archive_name = "$dependency_name.tar.gz"; my $binary_path = "s3://$s3_bucket_binary_dependency_name/$distro_type/$distro_version/$dependency_archive_name"; # It can be: x86_64 or aarch64 my $machine_architecture = `uname -m`; chomp $machine_architecture; # We added ARM platforms later and we use another path for them if ($machine_architecture eq 'aarch64') { $binary_path = "s3://$s3_bucket_binary_dependency_name/$machine_architecture/$distro_type/$distro_version/$dependency_archive_name"; } my $archive_res = system("tar --use-compress-program=pigz -cpf /tmp/$dependency_archive_name -C $library_install_folder $dependency_name"); if ($archive_res != 0) { print "Cannot pack dependency\n"; return ''; } my $upload_this_file = system("s3cmd --disable-multipart --host=storage.googleapis.com --host-bucket=\"%(bucket).storage.googleapis.com\" put /tmp/$dependency_archive_name $binary_path"); if ($upload_this_file != 0) { print "Cannot upload dependency file to /tmp/$dependency_archive_name Google Storage\n"; return ''; } print "Successfully uploaded\n"; print "Start sha 512 calculations\n"; my $sha512 = get_sha_512_sum("/tmp/$dependency_archive_name"); unless ($sha512) { print "Cannot calculate sha-512 for file\n"; return ''; } print "Successfully calculated sha-512 for $dependency_name: $sha512\n"; return 1 } sub exec_command { my $command = shift; open my $fl, ">>", $install_log_path or warn "Cannot open $install_log_path $!";; print {$fl} "We are calling command: $command\n\n"; my $output = `$command 2>&1`; print {$fl} "Command finished with code $?\n\n"; if ($? == 0) { return 1; } else { warn "Command $command call failed with code $? and output: $output\n"; return ''; } } sub get_sha1_sum { my $path = shift; my $hasher_name = ''; if ($os_type eq 'macosx') { $hasher_name = 'shasum'; } elsif ($os_type eq 'freebsd') { $hasher_name = 'sha1'; } else { # Linux $hasher_name = 'sha1sum'; } my $output = `$hasher_name $path`; chomp $output; my ($sha1) = ($output =~ m/^(\w+)\s+/); return $sha1; } sub get_sha_512_sum { my $path = shift; my $hasher_name = 'sha512sum'; my $output = `$hasher_name $path`; chomp $output; my ($sha_512) = ($output =~ m/^(\w+)\s+/); return $sha_512; } sub download_file { my ($url, $path, $expected_sha1_checksumm) = @_; my ($path_filename, $path_dirs, $path_suffix) = fileparse($path); my $file_path_in_cache = "$cache_folder/$path_filename"; if ($use_cache) { unless (-e $cache_folder) { my $mkdir_result = mkdir $cache_folder; unless ($mkdir_result) { warn "Can't create cache folder\n"; return ''; } } if (-e $file_path_in_cache) { if ($expected_sha1_checksumm) { my $calculated_checksumm = get_sha1_sum($file_path_in_cache); if ($calculated_checksumm eq $expected_sha1_checksumm) { my $copy_result = copy($file_path_in_cache, $path); if (!$copy_result) { warn "Could not copy file from cache to build folder\n"; } else { warn "Got archive $path_filename from cache\n"; # All fine! return 1; } } else { print "Archive from cache has incorrect sha1: $calculated_checksumm expected: $expected_sha1_checksumm\n"; } } else { my $copy_result = copy($file_path_in_cache, $path); if (!$copy_result) { warn "Could not copy file from cache to build folder\n"; } else { warn "Got archive $path_filename from cache\n"; # All fine! return 1; } } } `wget --no-check-certificate --quiet '$url' -O$path`; if ($? != 0) { print "We can't download archive $url correctly\n"; return ''; } } if ($expected_sha1_checksumm) { my $calculated_checksumm = get_sha1_sum($path); if ($calculated_checksumm eq $expected_sha1_checksumm) { if ($use_cache) { # Put file copy to cache folder my $copy_result = copy($path, $file_path_in_cache); if (!$copy_result) { warn "Copy to cache failed\n"; } } return 1; } else { print "Downloaded archive has incorrect sha1: $calculated_checksumm expected: $expected_sha1_checksumm\n"; return ''; } } else { if ($use_cache) { print "We will copy file from $path to $file_path_in_cache\n"; # Put file copy to cache folder my $copy_result = copy($path, $file_path_in_cache); if (!$copy_result) { warn "Copy to cache failed\n"; } } return 1; } } sub init_machine_information { my $machine_information = Fastnetmon::detect_distribution(); unless ($machine_information) { die "Could not collect machine information\n"; } $distro_version = $machine_information->{distro_version}; $distro_type = $machine_information->{distro_type}; $os_type = $machine_information->{os_type}; $distro_architecture = $machine_information->{distro_architecture}; $appliance_name = $machine_information->{appliance_name}; } # Installs all dependencies required for build process sub install_build_dependencies { my $machine_information = Fastnetmon::detect_distribution(); unless ($machine_information) { die "Could not collect machine information\n"; } my $distro_version = $machine_information->{distro_version}; my $distro_type = $machine_information->{distro_type}; # Install packages required for build if ($distro_type eq 'ubuntu' or $distro_type eq 'debian') { print "Update package manager cache\n"; exec_command("apt-get update"); print "Install packages\n"; apt_get('make', 'wget', 'git', 'pigz', 'bzip2', 'autoconf', 'libtool', 'pkg-config'); } elsif ( $distro_type eq 'centos') { # We need libmpc for our custom built gcc print "Install packages\n"; yum('make', 'wget', 'libmpc', 'glibc-devel', 'git', 'pigz', 'bzip2', 'autoconf', 'libtool', 'pkgconfig'); } print "Successfully installed all packages\n"; } # This code will init global compiler settings used in options for other packages build sub init_compiler { init_machine_information(); # 5_3_0 instead of 5.3.0 my $gcc_version_for_path = $gcc_version; $gcc_version_for_path =~ s/\./_/g; $gcc_c_compiler_path = "$library_install_folder/gcc_$gcc_version_for_path/bin/gcc"; $gcc_cpp_compiler_path = "$library_install_folder/gcc_$gcc_version_for_path/bin/g++"; $clang_c_compiler_path = "$library_install_folder/clang_7_0_0/bin/clang"; $clang_cpp_compiler_path = "$library_install_folder/clang_7_0_0/bin/clang++"; # Default compiler path if ($build_with_clang) { $default_c_compiler_path = $clang_c_compiler_path; $default_cpp_compiler_path = $clang_cpp_compiler_path; } else { $default_c_compiler_path = $gcc_c_compiler_path; $default_cpp_compiler_path = $gcc_cpp_compiler_path; } # Add new compiler to configure options # It's mandatory for log4cpp $configure_options = "CC=$default_c_compiler_path CXX=$default_cpp_compiler_path"; if ($use_libcpp_instead_stdcpp) { $configure_options = "$configure_options -stdlib=libc++"; } my @make_library_path_list_options = ("$library_install_folder/gcc_$gcc_version_for_path/lib64"); if ($use_libcpp_instead_stdcpp) { @make_library_path_list_options = ("$library_install_folder/clang_7_0_0/lib"); } $ld_library_path_for_make = "LD_LIBRARY_PATH=" . join ':', @make_library_path_list_options; # Also we should tune number of threads for make $cpus_number = get_logical_cpus_number(); # Boost and cmake compilation needs lots of memory, we need to reduce number of threads on CircleCI as it expose 32 threads but it's not real # Limit it by number of threads available on our plan: https://circleci.com/product/features/resource-classes/ if (defined($ENV{'CI'}) && $ENV{'CI'}) { if ($cpus_number > 4) { $cpus_number = 4; print "We run on CI and we need to cap number of CPU cores to $cpus_number as CircleCI does not report corect number of cores to us due to Docker use\n"; } } # We could get huge speed benefits with this option if ($cpus_number > 1) { $make_options = "-j $cpus_number"; } } sub install_bpf { my $folder_name = shift; my $libbpf_package_install_path = "$library_install_folder/$folder_name"; # TODO: # We need to get rid of these dependencies and link against our zlib and elfutils if ($distro_type eq 'ubuntu' || $distro_type eq 'debian') { my @dependency_list = ('libelf-dev', 'zlib1g-dev'); apt_get(@dependency_list); } elsif ($distro_type eq 'centos') { yum('elfutils-libelf-devel'); } my $elfutils_install_path = "$library_install_folder/elfutils_0_186"; my $zlib_path = "$library_install_folder/zlib_1_3_1"; my $archive_file_name = 'v1.0.1.tar.gz '; print "Download libbpf\n"; chdir $temp_folder_for_building_project; my $lib_bpf_download_result = download_file("https://github.com/libbpf/libbpf/archive/refs/tags/v1.0.1.tar.gz", $archive_file_name, '9350f196150892f544e0681cc6c1f78e603b5d95'); unless ($lib_bpf_download_result) { warn "Cannot download libbpf\n"; return ''; } print "Unpack libbpf\n"; unless (exec_command("tar -xf $archive_file_name")) { warn "Cannot unpack libbpf\n"; return ''; } chdir "libbpf-1.0.1/src"; print "Make bpf\n"; # Unfortunately, pkg-config does not accept multiple paths in PKG_CONFIG_PATH # And for now I decided to link against our own libelf but keep linking with standard zlib # PKG_CONFIG_PATH=\"$elfutils_install_path/lib/pkgconfig\" # unless (exec_command("$ld_library_path_for_make make")) { warn "Cannot make libbpf\n"; return ''; } print "Make install\n"; # We set prefix to "" as it's /usr by default and we do not need intermediate folder in install path unless (exec_command("PREFIX=\"\" DESTDIR=$libbpf_package_install_path $ld_library_path_for_make make install")) { warn "Cannot install libbpf\n"; return ''; } return 1; } sub install_gcc { my $folder_name = shift; my $gcc_package_install_path = "$library_install_folder/$folder_name"; if ($distro_type eq 'ubuntu' || $distro_type eq 'debian') { my @dependency_list = ('libmpfr-dev', 'libmpc-dev', 'libgmp-dev', 'gcc', 'g++', 'diffutils'); apt_get(@dependency_list); } elsif ($distro_type eq 'centos') { yum('gmp-devel', 'mpfr-devel', 'libmpc-devel', 'gcc', 'gcc-c++', 'diffutils'); } print "Download gcc archive\n"; chdir $temp_folder_for_building_project; my $archive_file_name = "gcc-$gcc_version.tar.gz"; my $gcc_download_result = download_file("http://ftp.mpi-sb.mpg.de/pub/gnu/mirror/gcc.gnu.org/pub/gcc/releases/gcc-$gcc_version/$archive_file_name", $archive_file_name, '7e79c695a0380ac838fa7c876a121cd28a73a9f5'); unless ($gcc_download_result) { warn "Can't download gcc sources\n"; return ''; } print "Unpack archive\n"; unless (exec_command("tar -xf $archive_file_name")) { warn 'Cannot create archive\n'; return ''; } # Remove source archive unlink "$archive_file_name"; unless (exec_command("mkdir $temp_folder_for_building_project/gcc-$gcc_version-objdir")) { warn "Cannot create build folder\n"; return ''; } chdir "$temp_folder_for_building_project/gcc-$gcc_version-objdir"; print "Configure build system\n"; unless (exec_command("$temp_folder_for_building_project/gcc-$gcc_version/configure --prefix=$gcc_package_install_path --enable-languages=c,c++ --disable-multilib")) { warn "Cannot configure gcc\n"; return ''; } print "Build gcc\n"; unless (exec_command("make $make_options")) { warn "Cannot make gcc\n"; return ''; } print "Install gcc\n"; unless (exec_command("make $make_options install")) { warn "Cannot install gcc\n"; return ''; } return 1; } sub install_boost { my $folder_name = shift; my $boost_version = '1.81.0'; my $boost_version_with_underscore = "1_81_0"; my $boost_install_path = "$library_install_folder/$folder_name"; chdir $temp_folder_for_building_project; my $archive_file_name = "boost_${boost_version_with_underscore}.tar.gz"; print "Download Boost source code\n"; my $boost_download_result = download_file("https://boostorg.jfrog.io/artifactory/main/release/$boost_version/source/boost_${boost_version_with_underscore}.tar.gz", $archive_file_name, '06d4bff547c1948fbdaf59b9d9d1399917ed0eb3'); unless ($boost_download_result) { warn "Can't download Boost source code\n"; return ''; } print "Unpack Boost source code\n"; exec_command("tar -xf $archive_file_name"); my $folder_name_inside_archive = "boost_$boost_version_with_underscore"; print "Fix permissions\n"; # Fix permissions because they are broken inside official archive exec_command("find $folder_name_inside_archive -type f -exec chmod 644 {} \\;"); exec_command("find $folder_name_inside_archive -type d -exec chmod 755 {} \\;"); exec_command("chown -R root:root $folder_name_inside_archive"); print "Remove archive\n"; unlink "$archive_file_name"; chdir $folder_name_inside_archive; my $boost_build_threads = $cpus_number; my $icu_path = "$library_install_folder/icu_65_1"; my $boost_build_path = "$library_install_folder/boost_build_4_9_2"; # More details about jam lookup: http://www.boost.org/build/doc/html/bbv2/overview/configuration.html my $content = "using gcc : $gcc_version_only_major : $default_cpp_compiler_path ;\n"; # We use non standard gcc compiler for Boost build and we need to specify it this way open my $fl, ">", "/root/user-config.jam" or die "Can't open $! file for writing manifest\n"; print {$fl} $content; close $fl; # When we run it with vzctl exec we have broken env and should put config in /etc too open my $etcfl, ">", "/etc/user-config.jam" or die "Can't open $! file for writing manifest\n"; print {$etcfl} $content; close $etcfl; print "Build Boost\n"; my $build_command = "$ld_library_path_for_make $boost_build_path/bin/b2 install -j $boost_build_threads -sICU_PATH=$icu_path linkflags=\"-Wl,-rpath,$icu_path/lib\" --build-dir=$temp_folder_for_building_project/boost_build_temp_directory link=shared --without-test --without-python --without-wave --without-log --without-mpi --without-graph --without-math --without-fiber --without-nowide --without-graph_parallel --without-json --without-type_erasure --without-coroutine --prefix=$boost_install_path"; print "Build command: $build_command\n"; my $b2_build_result = exec_command($build_command); unless ($b2_build_result) { warn "Can't execute b2 build correctly\n"; return ''; } 1; } sub install_boost_build { my $folder_name = shift; chdir $temp_folder_for_building_project; # We use another name because it uses same name as boost distribution my $archive_file_name = '4.9.2.tar.gz'; my $boost_builder_install_folder = "$library_install_folder/$folder_name"; print "Download boost builder\n"; my $boost_build_result = download_file("https://github.com/bfgroup/build/archive/$archive_file_name", $archive_file_name, '1c77d3fda9425fd89b783db8f7bd8ebecdf8f916'); unless ($boost_build_result) { warn("Can't download boost builder\n"); return ''; } print "Unpack boost builder\n"; exec_command("tar -xf $archive_file_name"); unless (chdir "b2-4.9.2") { warn("Cannot do chdir to build boost folder\n"); return ''; } # Due to this bug: # https://github.com/boostorg/build/issues/705 # I do not think that it actually fixed # We need to install system compiler if ($distro_type eq 'ubuntu' || $distro_type eq 'debian') { apt_get('g++'); } elsif ($distro_type eq 'centos') { yum('gcc-c++'); } print "Build Boost builder\n"; my $bootstrap_result = exec_command("$ld_library_path_for_make CC=$default_c_compiler_path CXX=$default_cpp_compiler_path ./bootstrap.sh --with-toolset=gcc"); unless ($bootstrap_result) { warn("bootstrap of Boost Builder failed, please check logs\n"); return ''; } my $b2_install_result = exec_command("$ld_library_path_for_make ./b2 install --prefix=$boost_builder_install_folder"); unless ($b2_install_result) { warn("Can't execute b2 install\n"); return ''; } 1; } sub install_log4cpp { my $folder_name = shift; my $log_cpp_version_short = '1.1.4rc3'; my $log4cpp_install_path = "$library_install_folder/$folder_name"; my $distro_file_name = "log4cpp-$log_cpp_version_short.tar.gz"; my $log4cpp_url = "https://sourceforge.net/projects/log4cpp/files/log4cpp-1.1.x%20%28new%29/log4cpp-1.1/log4cpp-$log_cpp_version_short.tar.gz/download"; chdir $temp_folder_for_building_project; print "Download log4cpp sources\n"; my $log4cpp_download_result = download_file($log4cpp_url, $distro_file_name, 'b32e6ec981a5d75864e1097525e1f502cc242d17'); unless ($log4cpp_download_result) { warn "Can't download log4cpp\n"; return ''; } print "Unpack log4cpp sources\n"; exec_command("tar -xf $distro_file_name"); chdir "$temp_folder_for_building_project/log4cpp"; print "Build log4cpp\n"; my $configure_result = ''; # We need to address bug on ARM 64 platforms: # configure: error: cannot guess build type; you must specify one # https://github.com/pavel-odintsov/fastnetmon/issues/980 my $log4cpp_configure_params = ''; # It can be: x86_64 or aarch64 my $machine_architecture = `uname -m`; chomp $machine_architecture; # We can specify build type manually # TODO: we need to report this solution to upstream: https://github.com/nzbget/nzbget/issues/418 if ($machine_architecture eq 'aarch64') { $log4cpp_configure_params = '--build=aarch64-unknown-linux-gnu'; } if ($configure_options) { $configure_result = exec_command("$configure_options ./configure --prefix=$log4cpp_install_path $log4cpp_configure_params"); } else { $configure_result = exec_command("./configure --prefix=$log4cpp_install_path $log4cpp_configure_params"); } if (!$configure_result) { die "Cannot configure log4cpp\n"; } my $make_result = exec_command("$ld_library_path_for_make make $make_options install"); if (!$make_result) { die "Make for log4cpp failed\n"; } 1; } sub install_pcap { my $folder_name = shift; print "Install packages\n"; if ($distro_type eq 'ubuntu' or $distro_type eq 'debian') { print "Update package manager cache\n"; exec_command("apt-get update"); apt_get('flex', 'bison'); } elsif ( $distro_type eq 'centos') { print "Install packages\n"; yum('flex', 'bison'); } my $res = install_configure_based_software("https://www.tcpdump.org/release/libpcap-1.10.4.tar.gz", "818cbe70179c73eebfe1038854665f33aac64245", "$library_install_folder/$folder_name", "--disable-usb --disable-netmap --disable-bluetooth --disable-dbus --disable-rdma "); unless ($res) { warn "Cannot install libpcap\n"; return ''; } return 1; } sub install_cares { my $folder_name = shift; my $res = install_configure_based_software("https://github.com/c-ares/c-ares/releases/download/cares-1_18_1/c-ares-1.18.1.tar.gz", "9e2a99af58d163d084db6fcebb2165a960bdd1af", "$library_install_folder/$folder_name", ""); unless ($res) { warn "Cannot install C-Ares\n"; return ''; } return 1; } sub install_zlib { my $folder_name = shift; my $res = install_configure_based_software("https://zlib.net/zlib-1.3.1.tar.gz", "f535367b1a11e2f9ac3bec723fb007fbc0d189e5", "$library_install_folder/$folder_name", ""); unless ($res) { warn "Cannot install zlib\n"; return ''; } return 1; } sub install_gtest { my $folder_name = shift; my $install_path = "$library_install_folder/$folder_name"; my $res = install_cmake_based_software("https://github.com/google/googletest/archive/refs/tags/v1.13.0.tar.gz", "bfa4b5131b6eaac06962c251742c96aab3f7aa78", $install_path, "$ld_library_path_for_make $cmake_path -DCMAKE_C_COMPILER=$default_c_compiler_path -DCMAKE_CXX_COMPILER=$default_cpp_compiler_path -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=$install_path .."); if (!$res) { warn "Cannot install gtest\n"; return ''; } return 1; } sub install_grpc { my $folder_name = shift; my $grpc_install_path = "$library_install_folder/$folder_name"; my $protobuf_install_path = "$library_install_folder/protobuf_21_12"; my $abseil_install_path = "$library_install_folder/abseil_2024_01_16"; my $openssl_path = "$library_install_folder/$openssl_folder_name"; my $cares_path = "$library_install_folder/cares_1_18_1"; my $zlib_path = "$library_install_folder/zlib_1_3_1"; my $re2_path = "$library_install_folder/re2_2022_12_01"; # There is a problem with official tar.gz from https://github.com/grpc/grpc/releases # When they prepare tar.gz they do not pull all required dependencies to third_party folder # https://github.com/grpc/grpc/issues/31760#issuecomment-1339944451 # Such a great finding that you actually need to explicitly provide -DBUILD_SHARED_LIBS=ON to build dynamic libraries # # I decided to explicitly set CMAKE_INSTALL_RPATH to lib folder of installed library as we're dealing with some weird linker issues for gRPC dependencies # (libupb.so.10, libaddress_sorting.so.10 and it actually solved these issues # # Then I added all dependency libraries into CMAKE_INSTALL_RPATH as we had weird linking issues with Cares # my $res = install_cmake_based_software("https://github.com/grpc/grpc/archive/v1.49.2.tar.gz", "28ba57cb3648812a48fd06c0de4b1e89d41e6934", $grpc_install_path, "$ld_library_path_for_make $cmake_path -DCMAKE_C_COMPILER=$default_c_compiler_path -DCMAKE_CXX_COMPILER=$default_cpp_compiler_path -DCMAKE_INSTALL_PREFIX=$grpc_install_path -DgRPC_PROTOBUF_PROVIDER=package -DCMAKE_INSTALL_RPATH=\"$grpc_install_path/lib;$cares_path/lib;$openssl_path/lib;$abseil_install_path/lib;$re2_path/lib64;$re2_path/lib\" -DCMAKE_PREFIX_PATH=\"$protobuf_install_path;$openssl_path;$cares_path;$abseil_install_path/lib/cmake/absl;$abseil_install_path/lib64/cmake/absl;$zlib_path;$re2_path/lib64/cmake/re2;$re2_path/lib/cmake/re2\" -DgRPC_ZLIB_PROVIDER=package -DgRPC_SSL_PROVIDER=package -DgRPC_ABSL_PROVIDER=package -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_CARES_PROVIDER=package -DgRPC_RE2_PROVIDER=package -DBUILD_SHARED_LIBS=ON .."); if (!$res) { warn "Can't install gRPC\n"; return ''; } return 1; } # Get git repository source code sub git_clone_repository { my ($repo_url, $local_folder, $repo_commit) = @_; my $git_clone_result = exec_command("git clone $repo_url $local_folder"); unless ($git_clone_result) { warn "Could not clone repository: $repo_url\n"; return ''; } # If we want certain commit if ($repo_commit ne 'master') { # Change current working directory to git repo chdir "$local_folder"; my $checkout_branch_result = exec_command("git checkout $repo_commit"); unless ($checkout_branch_result) { warn "Could not checkout commit $repo_commit for repository $repo_url\n"; return ''; } } return 1; } # We do not use cache for it yet sub install_gobgp { my $folder_name = shift; chdir $temp_folder_for_building_project; # It can be: x86_64 or aarch64 my $machine_architecture = `uname -m`; chomp $machine_architecture; my $distro_file_name = 'gobgp_3.12.0_linux_amd64.tar.gz'; my $gobgp_sha1 = 'eca957a8991b8ef6eceef665a9f15a3717827a09'; # We download pre compiled binaries and we need to download different file for ARM64 platform if ($machine_architecture eq 'aarch64') { $distro_file_name = 'gobgp_3.12.0_linux_arm64.tar.gz'; $gobgp_sha1 = 'ba42e5c7fb92638a7ced9d30fc20b24925e0a923'; } my $download_result = download_file("https://github.com/osrg/gobgp/releases/download/v3.12.0/$distro_file_name", $distro_file_name, $gobgp_sha1); unless ($download_result) { warn "Could not download gobgp\n"; return ''; } my $unpack_result = exec_command("tar -xf $distro_file_name"); unless ($unpack_result) { warn "Could not unpack gobgp\n"; return ''; } my $gobgp_install_path = "$library_install_folder/$folder_name"; mkdir "$gobgp_install_path"; `cp gobgp $gobgp_install_path`; `cp gobgpd $gobgp_install_path`; 1; } sub install_re2 { my $folder_name = shift; my $install_path = "$library_install_folder/$folder_name"; my $res = install_cmake_based_software("https://github.com/google/re2/archive/refs/tags/2022-12-01.tar.gz", "8146fb81e2b8988a455f2f7291c7a8a4001e55a6", "$library_install_folder/$folder_name", "$ld_library_path_for_make $cmake_path -DCMAKE_C_COMPILER=$default_c_compiler_path -DCMAKE_CXX_COMPILER=$default_cpp_compiler_path -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=$install_path .."); if (!$res) { warn "Cannot install re2\n"; return ''; } return 1; } sub install_protobuf { my $folder_name = shift; my $install_path = "$library_install_folder/$folder_name"; my $res = install_cmake_based_software("https://github.com/protocolbuffers/protobuf/releases/download/v21.12/protobuf-all-21.12.tar.gz", "5dcaabdc890593b1c9c5dc5646a26ff82593ccb9", "$library_install_folder/$folder_name", "$ld_library_path_for_make $cmake_path -DCMAKE_C_COMPILER=$default_c_compiler_path -DCMAKE_CXX_COMPILER=$default_cpp_compiler_path -Dprotobuf_BUILD_TESTS=OFF -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=$install_path .."); if (!$res) { warn "Cannot install Protobuf\n"; return ''; } return 1; } sub install_rdkafka { my $folder_name = shift; my $res = install_configure_based_software("https://github.com/edenhill/librdkafka/archive/v1.7.0.tar.gz", "d07d7f4ca8b969d90cb380c7d9e381690890e677", "$library_install_folder/$folder_name", "--disable-gssapi --disable-lz4-ext --disable-ssl"); unless ($res) { die "Cannot install librdkafka\n"; } return 1; } sub install_clickhouse { my $folder_name = shift; my $clickhouse_install_path = "$library_install_folder/$folder_name"; my $res = install_cmake_based_software("https://github.com/ClickHouse/clickhouse-cpp/archive/refs/tags/v2.3.0.tar.gz", "08a4a2e45ddcb33941c22f5e02177805d7fdd664", $clickhouse_install_path, "$ld_library_path_for_make $cmake_path -DCMAKE_INSTALL_PREFIX:STRING=$clickhouse_install_path -DCMAKE_C_COMPILER=$default_c_compiler_path -DCMAKE_CXX_COMPILER=$default_cpp_compiler_path -DBUILD_SHARED_LIBS:BOOL=ON .."); if (!$res) { die "Could not install libclickhouse\n"; } 1; } sub install_elfutils { my $folder_name = shift; if ($distro_type eq 'ubuntu' || $distro_type eq 'debian') { apt_get(('zlib1g-dev')); } elsif ($distro_type eq 'centos') { yum('zlib-devel', 'm4'); } my $res = install_configure_based_software("https://sourceware.org/pub/elfutils/0.186/elfutils-0.186.tar.bz2", "650d52024be684dabf18a5261a69836a16f84f72", "$library_install_folder/$folder_name", '--disable-debuginfod --disable-libdebuginfod' ); unless ($res) { warn "Cannot install elfutils\n"; return ''; } return 1; } sub install_capnproto { my $folder_name = shift; my $capnp_install_path = "$library_install_folder/$folder_name"; my $res = install_configure_based_software("https://capnproto.org/capnproto-c++-0.8.0.tar.gz", "fbc1c65b32748029f1a09783d3ebe9d496d5fcc4", $capnp_install_path, ''); unless ($res) { warn "Could not install capnproto\n"; return ''; } return 1; } sub install_abseil { my $folder_name = shift; my $install_path = "$library_install_folder/$folder_name"; # We need explicitly enable PIC to successfully build against gRPC # https://github.com/abseil/abseil-cpp/pull/741 # -DCMAKE_POSITION_INDEPENDENT_CODE=true my $res = install_cmake_based_software("https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.2.tar.gz", "bb8a766f3aef8e294a864104b8ff3fc37b393210", "$library_install_folder/$folder_name", "$ld_library_path_for_make $cmake_path -DCMAKE_C_COMPILER=$default_c_compiler_path -DCMAKE_CXX_COMPILER=$default_cpp_compiler_path -DABSL_BUILD_TESTING=OFF -DABSL_USE_GOOGLETEST_HEAD=ON -DABSL_ENABLE_INSTALL=ON -DCMAKE_CXX_STANDARD=14 -DCMAKE_POSITION_INDEPENDENT_CODE=true -DCMAKE_INSTALL_PREFIX=$install_path .."); if (!$res) { warn "Cannot install abseil\n"; return '' } return 1; } sub install_cppkafka { my $folder_name = shift; my $rdkafka_path = "$library_install_folder/rdkafka_1_7_0"; my $boost_path = "$library_install_folder/boost_1_81_0"; # We hardcode RPATH with CMAKE_INSTALL_RPATH to allow cppkafka to find rdkafka in our custom path automatically my $res = install_cmake_based_software("https://github.com/mfontanini/cppkafka/archive/v0.3.1.tar.gz", "0da8a4229dddf97cbf52a1a5ae7b99c923052edb", "$library_install_folder/$folder_name", "$ld_library_path_for_make $cmake_path -DRDKAFKA_ROOT_DIR=$rdkafka_path -DCPPKAFKA_DISABLE_TESTS=ON -DCMAKE_INSTALL_RPATH=$rdkafka_path/lib -DBOOST_ROOT=$boost_path -DCMAKE_C_COMPILER=$default_c_compiler_path -DCMAKE_CXX_COMPILER=$default_cpp_compiler_path -DCMAKE_INSTALL_PREFIX=$library_install_folder/$folder_name .."); if (!$res) { die "Can't install cppkafka\n"; } return 1; } sub install_mongo_c_driver { my $folder_name = shift; my $install_path = "$library_install_folder/$folder_name"; my $openssl_path = "$library_install_folder/$openssl_folder_name"; # OpenSSL is mandatory for SCRAM-SHA-1 auth mode # I also use flag ENABLE_ICU=OFF to disable linking against icu system library. I do no think that we really need it my $res = install_cmake_based_software("https://github.com/mongodb/mongo-c-driver/releases/download/1.23.0/mongo-c-driver-1.23.0.tar.gz", "f6256acfe89ed094158be84a3ce2a56fd7f22637", $install_path, "$ld_library_path_for_make $cmake_path -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:STRING=$library_install_folder/mongo_c_driver_1_23_0 -DCMAKE_C_COMPILER=$default_c_compiler_path -DOPENSSL_ROOT_DIR=$openssl_path -DCMAKE_CXX_COMPILER=$default_cpp_compiler_path -DENABLE_ICU=OFF -DMONGOC_TEST_USE_CRYPT_SHARED=OFF .."); if (!$res) { warn "Could not install mongo c client\n"; return ''; } return 1; } sub install_configure_based_software { my ($url_to_archive, $sha1_summ_for_archive, $library_install_path, $configure_options) = @_; unless ($url_to_archive && $sha1_summ_for_archive && $library_install_path) { warn "You haven't specified all mandatory arguments for install_configure_based_software\n"; return ''; } unless (defined($configure_options)) { $configure_options = ''; } chdir $temp_folder_for_building_project; my $file_name = get_file_name_from_url($url_to_archive); unless ($file_name) { warn "Could not extract file name from URL $url_to_archive\n"; return ''; } print "Download archive\n"; my $archive_download_result = download_file($url_to_archive, $file_name, $sha1_summ_for_archive); unless ($archive_download_result) { warn "Could not download URL $url_to_archive\n"; return ''; } unless (-e $file_name) { warn "Could not find downloaded file in current folder\n"; return ''; } print "Read file list inside archive\n"; my $folder_name_inside_archive = get_folder_name_inside_archive("$temp_folder_for_building_project/$file_name"); unless ($folder_name_inside_archive) { warn "We could not extract folder name from tar archive '$temp_folder_for_building_project/$file_name'\n"; return ''; } print "Unpack archive\n"; my $unpack_result = exec_command("tar -xf $file_name"); unless ($unpack_result) { warn "Unpack failed\n"; return ''; } chdir $folder_name_inside_archive; unless (-e "configure") { warn "We haven't configure script here\n"; return ''; } print "Execute configure\n"; my $configure_command = "CC=$default_c_compiler_path CXX=$default_cpp_compiler_path ./configure --prefix=$library_install_path $configure_options"; my $configure_result = exec_command($configure_command); unless ($configure_result) { warn "Configure failed"; return ''; } ### TODO: this is ugly thing! But here you could find hack for poco libraries if ($url_to_archive =~ m/Poco/i) { exec_command("sed -i 's#^CC .*#CC = $default_c_compiler_path#' build/config/Linux"); exec_command("sed -i 's#^CXX .*#CXX = $default_cpp_compiler_path#' build/config/Linux"); #print `cat build/config/Linux`; } # librdkafka does not like make+make install approach, we should use them one by one print "Execute make\n"; my $make_result = exec_command("$ld_library_path_for_make make $make_options"); unless ($make_result) { warn "Make failed\n"; return ''; } print "Execute make install\n"; # We explicitly added path to library folders from our custom compiler here my $make_install_result = exec_command("$ld_library_path_for_make make install"); unless ($make_install_result) { warn "Make install failed\n"; return ''; } return 1; } sub install_openssl { my $folder_name = shift; my $distro_file_name = 'openssl-1.1.1q.tar.gz'; my $openssl_install_path = "$library_install_folder/$folder_name"; chdir $temp_folder_for_building_project; my $openssl_download_result = download_file("https://www.openssl.org/source/$distro_file_name", $distro_file_name, '79511a8f46f267c533efd32f22ad3bf89a92d8e5'); unless ($openssl_download_result) { warn "Could not download openssl"; return ''; } exec_command("tar -xf $distro_file_name"); chdir "openssl-1.1.1q"; exec_command("CC=$default_c_compiler_path ./config shared --prefix=$openssl_install_path"); exec_command("$ld_library_path_for_make make -j $make_options"); exec_command("$ld_library_path_for_make make install"); 1; } sub install_icu { my $folder_name = shift; my $distro_file_name = 'icu4c-65_1-src.tgz'; chdir $temp_folder_for_building_project; my $icu_install_path = "$library_install_folder/$folder_name"; print "Download icu\n"; my $icu_download_result = download_file("https://github.com/unicode-org/icu/releases/download/release-65-1/$distro_file_name", $distro_file_name, 'd1e6b58aea606894cfb2495b6eb1ad533ccd2a25'); unless ($icu_download_result) { warn "Could not download ibicu"; return ''; } print "Unpack icu\n"; exec_command("tar -xf $distro_file_name"); chdir "icu/source"; print "Build icu\n"; exec_command("$configure_options ./configure --prefix=$icu_install_path"); exec_command("$ld_library_path_for_make make $make_options"); exec_command("$ld_library_path_for_make make $make_options install"); 1; } sub install_cmake { my $folder_name = shift; print "Install cmake\n"; my $cmake_install_path = "$library_install_folder/$folder_name"; warn "Cannot get dependency from cache, do manual build\n"; my $distro_file_name = "cmake-3.23.4.tar.gz"; chdir $temp_folder_for_building_project; print "Download archive\n"; my $cmake_download_result = download_file("https://github.com/Kitware/CMake/releases/download/v3.23.4/$distro_file_name", $distro_file_name, '05957280718e068df074f76c89cba77de1ddd4a2'); unless ($cmake_download_result) { warn "Can't download cmake\n"; return ''; } exec_command("tar -xf $distro_file_name"); chdir "cmake-3.23.4"; my $openssl_path = "$library_install_folder/$openssl_folder_name"; print "Execute bootstrap, it will need time\n"; my $boostrap_result = exec_command("$ld_library_path_for_make CC=$default_c_compiler_path CXX=$default_cpp_compiler_path ./bootstrap --prefix=$cmake_install_path --parallel=$cpus_number -- -DOPENSSL_ROOT_DIR=$openssl_path"); unless ($boostrap_result) { warn("Cannot run bootstrap\n"); return ''; } print "Make it\n"; my $make_command = "$ld_library_path_for_make make $make_options"; my $make_result = exec_command($make_command); unless ($make_result) { warn "Make command '$make_command' failed\n"; return ''; } print "Make install it\n"; exec_command("$ld_library_path_for_make make install"); return 1; } # Extract file name from URL # https://github.com/mongodb/mongo-cxx-driver/archive/r3.0.0-rc0.tar.gz => r3.0.0-rc0.tar.gz sub get_file_name_from_url { my $url = shift; # Remove prefix $url =~ s#https?://##; my @components = split '/', $url; return $components[-1]; } sub install_cmake_based_software { my ($url_to_archive, $sha1_summ_for_archive, $library_install_path, $cmake_with_options) = @_; unless ($url_to_archive && $sha1_summ_for_archive && $library_install_path && $cmake_with_options) { return ''; } chdir $temp_folder_for_building_project; my $file_name = get_file_name_from_url($url_to_archive); unless ($file_name) { warn "Could not extract file name from URL $url_to_archive"; return ''; } print "Download archive\n"; my $archive_download_result = download_file($url_to_archive, $file_name, $sha1_summ_for_archive); unless ($archive_download_result) { warn "Could not download URL $url_to_archive\n"; return ''; } unless (-e $file_name) { warn "Could not find downloaded file in current folder\n"; return ''; } print "Read file list inside archive\n"; my $folder_name_inside_archive = get_folder_name_inside_archive("$temp_folder_for_building_project/$file_name"); unless ($folder_name_inside_archive) { warn "We could not extract folder name from tar archive: $temp_folder_for_building_project/$file_name\n"; return ''; } print "Unpack archive\n"; my $unpack_result = exec_command("tar --no-same-owner -xf $file_name"); unless ($unpack_result) { warn "Unpack failed\n"; return ''; } chdir $folder_name_inside_archive; unless (-e "CMakeLists.txt") { warn "We haven't CMakeLists.txt in top project folder! Could not build project\n"; return ''; } unless (-e "build") { mkdir "build"; } chdir "build"; print "Generate make file with cmake\n"; # print "cmake command: $cmake_with_options\n"; my $cmake_result = exec_command($cmake_with_options); unless ($cmake_result) { warn "cmake command failed\n"; return ''; } print "Build project with make\n"; my $make_command = "$ld_library_path_for_make make $make_options"; my $make_result = exec_command($make_command); unless ($make_result) { warn "Make command '$make_command' failed\n"; return ''; } print "Install project to target directory\n"; my $install_result = exec_command("$ld_library_path_for_make make install"); unless ($install_result) { warn "Install failed\n"; return ''; } return 1; } # Get folder name from archive sub get_folder_name_inside_archive { my $file_path = shift; unless ($file_path && -e $file_path) { return ''; } my $tar = Archive::Tar->new; $tar->read($file_path); for my $file($tar->list_files()) { # if name has / in the end we could assume it's folder if ($file =~ m#/$#) { return $file; } } # For some reasons we can have case when we do not have top level folder alone but we can extract it from path: # libcmime-0.2.1/VERSION for my $file($tar->list_files()) { # if name has / in the end we could assume it's folder if ($file =~ m#(.*?)/.*+$#) { return $1; } } return ''; } sub install_hiredis { my $folder_name = shift; my $disto_file_name = 'v0.14.0.tar.gz'; my $hiredis_install_path = "$library_install_folder/$folder_name"; chdir $temp_folder_for_building_project; print "Download hiredis\n"; my $hiredis_download_result = download_file("https://github.com/redis/hiredis/archive/$disto_file_name", $disto_file_name, 'd668b86756d2c68f0527e845dc10ace5a053bbd9'); unless ($hiredis_download_result) { warn "Can't download hiredis\n"; return ''; } exec_command("tar -xf $disto_file_name"); print "Build hiredis\n"; chdir "hiredis-0.14.0"; exec_command("PREFIX=$hiredis_install_path make $make_options install"); 1; } sub read_file { my $file_name = shift; my $res = open my $fl, "<", $file_name; unless ($res) { return ""; } my $content = join '', <$fl>; chomp $content; return $content; } sub apt_get { my @packages_list = @_; # We install one package per apt-get call because installing multiple packages in one time could fail of one package is broken for my $package (@packages_list) { exec_command("DEBIAN_FRONTEND=noninteractive apt-get install -y --force-yes $package"); if ($? != 0) { print "Package '$package' install failed with code $?\n" } } } sub yum { my @packages_list = @_; for my $package (@packages_list) { exec_command("yum install -y $package"); if ($? != 0) { print "Package '$package' install failed with code $?\n"; } } } # Gets OS type for our purposes sub get_os_type { my $os_type = ''; my $uname_s_output = `uname -s`; chomp $uname_s_output; # uname -a output examples: # FreeBSD 10.1-STABLE FreeBSD 10.1-STABLE #0 r278618: Thu Feb 12 13:55:09 UTC 2015 root@:/usr/obj/usr/src/sys/KERNELWITHNETMAP amd64 # Darwin MacBook-Pro-Pavel.local 14.5.0 Darwin Kernel Version 14.5.0: Wed Jul 29 02:26:53 PDT 2015; root:xnu-2782.40.9~1/RELEASE_X86_64 x86_64 # Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:43:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux if ($uname_s_output =~ /FreeBSD/) { $os_type = 'freebsd'; } elsif ($uname_s_output =~ /Darwin/) { $os_type = 'macosx'; } elsif ($uname_s_output =~ /Linux/) { $os_type = 'linux'; } else { warn "Can't detect platform operating system\n"; } return $os_type; } sub get_logical_cpus_number { my $os_type = get_os_type(); if ($os_type eq 'linux') { my @cpuinfo = `cat /proc/cpuinfo`; chomp @cpuinfo; my $cpus_number = scalar grep {/processor/} @cpuinfo; return $cpus_number; } elsif ($os_type eq 'macosx' or $os_type eq 'freebsd') { my $cpus_number = `sysctl -n hw.ncpu`; chomp $cpus_number; } else { warn "Unknown platform: $os_type Cannot get number of CPUs"; return 1; } } # Detect operating system of this machine sub detect_distribution { my $distro_type = ''; my $distro_version = ''; my $appliance_name = ''; my $distro_architecture = ''; my $os_type = get_os_type(); if ($os_type eq 'linux') { # x86_64 or i686 $distro_architecture = `uname -m`; chomp $distro_architecture; if (-e "/etc/debian_version") { # Well, on this step it could be Ubuntu or Debian # We need check issue for more details my @issue = `cat /etc/issue`; chomp @issue; my $issue_first_line = $issue[0]; # Possible /etc/issue contents: # Debian GNU/Linux 8 \n \l # Ubuntu 14.04.2 LTS \n \l # Welcome to VyOS - \n \l my $is_proxmox = ''; # Really hard to detect https://github.com/proxmox/pve-manager/blob/master/bin/pvebanner for my $issue_line (@issue) { if ($issue_line =~ m/Welcome to the Proxmox Virtual Environment/) { $is_proxmox = 1; $appliance_name = 'proxmox'; last; } } if ($issue_first_line =~ m/Debian/ or $is_proxmox) { $distro_type = 'debian'; $distro_version = `cat /etc/debian_version`; chomp $distro_version; # # Debian 6 example: 6.0.10 # We will try transform it to decimal number if ($distro_version =~ /^(\d+)\.\d+\.\d+$/) { $distro_version = $1; } elsif ($distro_version =~ /^(\d+)\.\d+$/) { # Examples: 9.13, 10.13, 11.5 $distro_version = $1; } } elsif ($issue_first_line =~ m/Ubuntu (\d+(?:\.\d+)?)/) { $distro_type = 'ubuntu'; $distro_version = $1; } elsif ($issue_first_line =~ m/VyOS/) { # Yes, VyOS is a Debian $distro_type = 'debian'; $appliance_name = 'vyos'; my $vyos_distro_version = `cat /etc/debian_version`; chomp $vyos_distro_version; # VyOS have strange version and we should fix it if ($vyos_distro_version =~ /^(\d+)\.\d+\.\d+$/) { $distro_version = $1; } } } if (-e "/etc/redhat-release") { $distro_type = 'centos'; my $distro_version_raw = `cat /etc/redhat-release`; chomp $distro_version_raw; # CentOS 6: # CentOS release 6.6 (Final) # CentOS 7: # CentOS Linux release 7.0.1406 (Core) # Fedora release 21 (Twenty One) if ($distro_version_raw =~ /(\d+)/) { $distro_version = $1; } } if (-e "/etc/gentoo-release") { $distro_type = 'gentoo'; my $distro_version_raw = `cat /etc/gentoo-release`; chomp $distro_version_raw; } unless ($distro_type) { die "This distro is unsupported, please do manual install"; } print "We detected your OS as $distro_type Linux $distro_version\n"; } elsif ($os_type eq 'macosx') { my $mac_os_versions_raw = `sw_vers -productVersion`; chomp $mac_os_versions_raw; if ($mac_os_versions_raw =~ /(\d+\.\d+)/) { $distro_version = $1; } print "We detected your OS as Mac OS X $distro_version\n"; } elsif ($os_type eq 'freebsd') { my $freebsd_os_version_raw = `uname -r`; chomp $freebsd_os_version_raw; if ($freebsd_os_version_raw =~ /^(\d+)\.?/) { $distro_version = $1; } print "We detected your OS as FreeBSD $distro_version\n"; } return { 'distro_version' => $distro_version, 'distro_type' => $distro_type, 'os_type' => $os_type, 'distro_architecture' => $distro_architecture, 'appliance_name' => $appliance_name, }; } sub install_package_by_name { my $handler_name = shift; # libname_1_2_3 my $full_package_name = shift; unless (defined( &{ "install_$handler_name" } )) { die "We have no handler function $handler_name for this library $full_package_name\n"; } no strict 'refs'; my $return_code = &{ "install_$handler_name"}($full_package_name); use strict 'refs'; return $return_code; } 1; upstream-fastnetmon/src/scripts/ipfix_csv_processor.pl0000775000175000017500000001233715060514305021574 0ustar meme#!/usr/bin/perl use strict; use warnings; # This script can convert data from http://www.iana.org/assignments/ipfix/ipfix.xhtml ipfix standard # represented in CSV form into form suitable for us # http://www.iana.org/assignments/ipfix/ipfix-information-elements.csv # to our C/C++ friendly storage format open my $fl, "<", "ipfix_fields.csv" or die "Can't open input file"; open my $target_h_file, ">", "ipfix_rfc.hpp" or die "Can't open file"; open my $target_cpp_file, ">", "ipfix_rfc.cpp" or die "Can't open file"; my $type_length_hash = { unsigned8 => 1, unsigned16 => 2, unsigned32 => 4, signed32 => 4, unsigned64 => 8, ipv4Address => 4, ipv6Address => 16, # types with unknown or variable length basicList => 0, boolean => 0, dateTimeMicroseconds => 0, dateTimeMilliseconds => 0, dateTimeNanoseconds => 0, dateTimeSeconds => 0, float64 => 0, macAddress => 0, octetArray => 0, string => 0, subTemplateList => 0, subTemplateMultiList => 0, }; my $field_id = 0; my @intervals = (); my $fields = {}; while (<$fl>) { chomp; if (/^(\d+\-\d+)/) { push @intervals, $1; } my $we_will_process_this_line = /^(\d+),/; # Skip descriptions and other crap unless ($we_will_process_this_line) { next; } # Numbers should growth monotonous if ($1 < $field_id) { next; } $field_id++; my @keys = ("id", "name", "data_type", "data_type_semantics", "status", "description", "units", "range", "reference", "requester", "revision", "date"); my %data = (); @data{ @keys } = split /,/, $_; #print "$data{id} $data{name} $data{data_type}\n"; $fields->{$data{"id"}} = \%data; } for my $interval (@intervals) { my ($interval_start, $interval_end) = $interval =~ /^(\d+)\-(\d+)$/; # Skip reserved interval up to 32767 because it's so long # Example: 503-32767 Unassigned next if $interval_end == 32767; for my $field_id ($interval_start .. $interval_end) { $fields->{$field_id} = { id => $field_id, name => 'Reserved' }; } # print "Interval start $interval_start end $interval_end\n"; } for my $field_id (sort { $a <=> $b } keys %$fields) { my $data = $fields->{$field_id}; if ($data->{name} eq 'Reserved') { #print "$data->{id} $data->{name}\n"; } else { #print "$data->{id} $data->{name} $data->{data_type}\n"; } } print {$target_h_file} < #include class ipfix_information_element_t { public: ipfix_information_element_t(std::string name, unsigned int length); ipfix_information_element_t(); std::string get_name(); unsigned int get_length(); std::string name; unsigned int length; }; typedef std::map ipfix_database_t; class ipfix_information_database { public: ipfix_information_database(); bool add_element(unsigned int field_id, std::string name, unsigned int length); std::string get_name_by_id(unsigned int field_id); unsigned int get_length_by_id(unsigned int field_id); private: ipfix_database_t database; }; DOC print {$target_cpp_file} < #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() { DOC for my $field_id (sort { $a <=> $b } keys %$fields) { my $data = $fields->{$field_id}; my $field_length = 0; my $field_name = '"reserved"'; if ($data->{"data_type"}) { $field_length = $type_length_hash->{ $data->{"data_type"} }; } if (defined($data->{"name"}) && $data->{"name"}) { $field_name = "\"$data->{'name'}\""; } print {$target_cpp_file} " this->add_element($field_id, $field_name, $field_length);\n"; } print {$target_cpp_file} "}\n"; upstream-fastnetmon/src/scripts/fastnetmon_notify.py0000775000175000017500000000362215060514305021263 0ustar meme#!/usr/bin/python import smtplib import sys from sys import stdin import optparse import sys import logging LOG_FILE = "/var/log/fastnetmon-notify.log" MAIL_HOSTNAME="localhost" MAIL_FROM="infra@example.com" MAIL_TO="infra@example.com" logger = logging.getLogger("DaemonLog") logger.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler = logging.FileHandler(LOG_FILE) handler.setFormatter(formatter) logger.addHandler(handler) client_ip_as_string=sys.argv[1] data_direction=sys.argv[2] pps_as_string=int(sys.argv[3]) action=sys.argv[4] logger.info(" - " . join(sys.argv)) def mail(subject, body): fromaddr = MAIL_FROM toaddrs = [MAIL_TO] # Add the From: and To: headers at the start! headers = ("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % ( fromaddr, ", ".join(toaddrs), subject ) ) msg = headers + body server = smtplib.SMTP(MAIL_HOSTNAME) #server.set_debuglevel(1) server.sendmail(fromaddr, toaddrs, msg) server.quit() if action == "unban": subject = "FastNetMon Community: IP %(client_ip_as_string)s unblocked because %(data_direction)s attack with power %(pps_as_string)d pps" % { 'client_ip_as_string': client_ip_as_string, 'data_direction': data_direction, 'pps_as_string' : pps_as_string, 'action' : action } mail(subject, "unban") sys.exit(0) elif action == "ban": subject = "FastNetMon Community: IP %(client_ip_as_string)s blocked because %(data_direction)s attack with power %(pps_as_string)d pps" % { 'client_ip_as_string': client_ip_as_string, 'data_direction': data_direction, 'pps_as_string' : pps_as_string, 'action' : action } body = "".join(sys.stdin.readlines()) mail(subject, body) sys.exit(0) else: sys.exit(0) upstream-fastnetmon/src/scripts/notify_with_discord.sh0000775000175000017500000000261415060514305021551 0ustar meme#!/usr/bin/env bash # Instructions: # # - Copy this script to /usr/local/bin/ # - Edit /etc/fastnetmon.conf and set: # notify_script_path = /usr/local/bin/notify_with_discord.sh # - Add your Discord channel webhook to discord_url. # # Notes: # Hostname lookup requires the dig command. # Debian: apt install dnsutils # Redhat: dnf install bind-utils fastnetmon_ip="$1" fastnetmon_direction="$2" fastnetmon_pps="$3" fastnetmon_action="$4" target_hostname=`dig -x $fastnetmon_ip +short` webhook_url="" message_username="FastNetMon" message_title="FastNetMon Alert - $fastnetmon_direction Attack" if [ -z "$fastnetmon_ip" ] || [ -z "$webhook_url" ]; then echo "Webhook URL / IP not set" exit 1 fi if [ "$fastnetmon_action" = "ban" ]; then # Read data from stdin cat > /dev/null color="14425373" elif [ "$fastnetmon_action" = "unban" ]; then color="3857437" else color="1957075" fi discord_payload="{\"username\": \"$message_username\", \"embeds\": [ { \"title\": \"$message_title\", \"color\": \"$color\", \"fields\": [ {\"name\": \"IP\", \"value\": \"$fastnetmon_ip\n$target_hostname\", \"inline\": true}, {\"name\": \"PPS\", \"value\": \"$fastnetmon_pps\", \"inline\": true}, {\"name\": \"Action Taken\", \"value\": \"$fastnetmon_action\"} ] } ] }" curl --connect-timeout 30 --max-time 60 -s -S -X POST -H 'Content-type: application/json' --data "$discord_payload" "$webhook_url" upstream-fastnetmon/src/scripts/build_library_bundle.pl0000775000175000017500000001316515060514305021657 0ustar meme#!/usr/bin/perl use strict; use warnings; use Data::Dumper; use File::Copy; use File::Basename; use File::Path qw(make_path); # # This script will produce binary archive with all libraries required for FastNetMon # # Script params: path to bundle archive # unless (scalar @ARGV == 1 && $ARGV[0]) { die "Please provide path to bundle file\n"; } my $archive_bundle_name = $ARGV[0]; if (-e $archive_bundle_name) { warn "Bundle file is already exists but we could remove it automatically\n"; unlink $archive_bundle_name; } # It may be different for community edition my $project_folder_name = 'fastnetmon-community'; my $global_path = "/opt/$project_folder_name"; my $fastnetmon_libraries_path = "$global_path/libraries"; my $temp_build_folder_path = `mktemp -d`; chomp $temp_build_folder_path; print "We will create temp folder for build: $temp_build_folder_path\n"; unless (-e $temp_build_folder_path && -d $temp_build_folder_path) { die "Can't create temporary folder for build\n"; } # Path to FastNetMon's folder int temp folder my $temp_folder_global_path = "$temp_build_folder_path/$project_folder_name"; # Open path which has all developer libraries (including binaries) opendir my $fastnetmon_libs_handle, $fastnetmon_libraries_path or die "Could not open library directory $fastnetmon_libraries_path with error: $!"; # List all files and folders in directory excluding special files . and .. my @libraries_list = grep { !/^\.+$/ } readdir $fastnetmon_libs_handle; closedir $fastnetmon_libs_handle; # Iterate all libraries in developer folder for my $library (@libraries_list) { my $library_path = "$fastnetmon_libraries_path/$library"; unless (-e $library_path) { die "Can't find library $library at path $library_path please check\n"; } # We do not need these libraries on customer machines if ($library =~ m/clang/) { next; } # print "Library: $library\n"; my @files = `find $library_path`; for my $file_full_path (@files) { chomp $file_full_path; if ($file_full_path =~ /\.so[\.\d]*/) { # Skip some unrelated files captured by reg exp if ($file_full_path =~ m/\.json$/ or $file_full_path =~ m/\.cpp$/) { next; } my $dir_name = dirname($file_full_path); my $file_name = basename($file_full_path); # print "$dir_name $file_name\n"; my $target_full_path = $file_full_path; $target_full_path =~ s#^$global_path#$temp_folder_global_path#; # Create target folder my $target_full_folder_path = $dir_name; $target_full_folder_path =~ s#^$global_path#$temp_folder_global_path/#; unless (-e $target_full_folder_path) { # print "Create folder $target_full_folder_path\n"; make_path( $target_full_folder_path ); } if (-l $file_full_path) { my $symlink_target_name = readlink($file_full_path); #print "We have symlink which aims to $symlink_target_name\n"; # This way we copy symlinks my $symlink_result = symlink($symlink_target_name, $target_full_path); unless ($symlink_result) { die "Symlink from $symlink_target_name to $target_full_path failed\n"; } } else { copy($file_full_path, $target_full_folder_path); # Strip debug information from library, we need it to reduce distribution size # It's pretty serious disk space saving (from 260Mb to 95Mb in my tests) system("strip --strip-debug $target_full_path"); } } } } sub list_files_in_folder { my $folder_path = shift; opendir my $fastnetmon_libs_handle, $folder_path or die "Could not open directory: $folder_path"; my @libraries_list = grep { !/^\.+$/ } readdir $fastnetmon_libs_handle; closedir $fastnetmon_libs_handle; return [ @libraries_list ]; } ### Copy binary files my $binary_files = list_files_in_folder("$global_path/app/bin"); mkdir "$temp_folder_global_path/app"; mkdir "$temp_folder_global_path/app/bin"; for my $binary_file (@$binary_files) { my $source_full_file_name = "$global_path/app/bin/$binary_file"; my $target_full_file_name = "$temp_folder_global_path/app/bin/$binary_file"; my $copy_result = copy($source_full_file_name, $target_full_file_name); unless ($copy_result) { die "Could not copy binary file from $source_full_file_name to $target_full_file_name\n"; } chmod 0755, $target_full_file_name; } # Install GoBGP binary files my $gobgp_folder_name = "gobgp_3_12_0"; mkdir "$temp_folder_global_path/libraries/$gobgp_folder_name"; for my $gobgp_binary ('gobgp', 'gobgpd') { unless (-e "$fastnetmon_libraries_path/$gobgp_folder_name/$gobgp_binary") { die "GoBGP binary $gobgp_binary does not exist\n"; } my $gobgp_copy_result = copy("$fastnetmon_libraries_path/$gobgp_folder_name/$gobgp_binary", "$temp_folder_global_path/libraries/$gobgp_folder_name/$gobgp_binary"); unless ($gobgp_copy_result) { die "Could not copy GoBGP's binary $gobgp_binary $!\n"; } # Enable exec flag chmod 0755, "$temp_folder_global_path/libraries/$gobgp_folder_name/$gobgp_binary"; } # Just tar it. We do not need compression here because this bundle will be unpacked by our own code later # I explicitly set uid and git to zero here to prevent any non zero owners for production build `tar -cpf $archive_bundle_name -C $temp_build_folder_path --owner=0 --group=0 ./`; print "We have created bundle $archive_bundle_name\n"; upstream-fastnetmon/src/conanfile.txt0000664000175000017500000000053715060514305016152 0ustar meme[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 upstream-fastnetmon/src/fmt/0000755000175000017500000000000014234265666014251 5ustar memeupstream-fastnetmon/src/fmt/format.h0000644000175000017500000031221314234265666015714 0ustar meme/* Formatting library for C++ Copyright (c) 2012 - present, Victor Zverovich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- Optional exception to the license --- As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into a machine-executable object form of such source code, you may redistribute such embedded portions in such object form without including the above copyright and permission notices. */ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ #include // std::signbit #include // uint32_t #include // std::numeric_limits #include // std::uninitialized_copy #include // std::runtime_error #include // std::system_error #include // std::swap #include "core.h" #ifdef __INTEL_COMPILER # define FMT_ICC_VERSION __INTEL_COMPILER #elif defined(__ICL) # define FMT_ICC_VERSION __ICL #else # define FMT_ICC_VERSION 0 #endif #ifdef __NVCC__ # define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) #else # define FMT_CUDA_VERSION 0 #endif #ifdef __has_builtin # define FMT_HAS_BUILTIN(x) __has_builtin(x) #else # define FMT_HAS_BUILTIN(x) 0 #endif #if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_NOINLINE __attribute__((noinline)) #else # define FMT_NOINLINE #endif #if FMT_MSC_VER # define FMT_MSC_DEFAULT = default #else # define FMT_MSC_DEFAULT #endif #ifndef FMT_THROW # if FMT_EXCEPTIONS # if FMT_MSC_VER || FMT_NVCC FMT_BEGIN_NAMESPACE namespace detail { template inline void do_throw(const Exception& x) { // Silence unreachable code warnings in MSVC and NVCC because these // are nearly impossible to fix in a generic code. volatile bool b = true; if (b) throw x; } } // namespace detail FMT_END_NAMESPACE # define FMT_THROW(x) detail::do_throw(x) # else # define FMT_THROW(x) throw x # endif # else # define FMT_THROW(x) \ do { \ FMT_ASSERT(false, (x).what()); \ } while (false) # endif #endif #if FMT_EXCEPTIONS # define FMT_TRY try # define FMT_CATCH(x) catch (x) #else # define FMT_TRY if (true) # define FMT_CATCH(x) if (false) #endif #ifndef FMT_DEPRECATED # if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 # define FMT_DEPRECATED [[deprecated]] # else # if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) # define FMT_DEPRECATED __attribute__((deprecated)) # elif FMT_MSC_VER # define FMT_DEPRECATED __declspec(deprecated) # else # define FMT_DEPRECATED /* deprecated */ # endif # endif #endif // Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. #if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC # define FMT_DEPRECATED_ALIAS #else # define FMT_DEPRECATED_ALIAS FMT_DEPRECATED #endif #ifndef FMT_USE_USER_DEFINED_LITERALS // EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. # if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ FMT_MSC_VER >= 1900) && \ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) # define FMT_USE_USER_DEFINED_LITERALS 1 # else # define FMT_USE_USER_DEFINED_LITERALS 0 # endif #endif // Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of // integer formatter template instantiations to just one by only using the // largest integer type. This results in a reduction in binary size but will // cause a decrease in integer formatting performance. #if !defined(FMT_REDUCE_INT_INSTANTIATIONS) # define FMT_REDUCE_INT_INSTANTIATIONS 0 #endif // __builtin_clz is broken in clang with Microsoft CodeGen: // https://github.com/fmtlib/fmt/issues/519 #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER # define FMT_BUILTIN_CLZ(n) __builtin_clz(n) #endif #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll)) && !FMT_MSC_VER # define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) #endif #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz)) # define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) #endif #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctzll)) # define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) #endif #if FMT_MSC_VER # include // _BitScanReverse[64], _BitScanForward[64], _umul128 #endif // Some compilers masquerade as both MSVC and GCC-likes or otherwise support // __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the // MSVC intrinsics if the clz and clzll builtins are not available. #if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(FMT_BUILTIN_CTZLL) FMT_BEGIN_NAMESPACE namespace detail { // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. # if !defined(__clang__) # pragma managed(push, off) # pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) # if defined(_WIN64) # pragma intrinsic(_BitScanForward64) # pragma intrinsic(_BitScanReverse64) # endif # endif inline auto clz(uint32_t x) -> int { unsigned long r = 0; _BitScanReverse(&r, x); FMT_ASSERT(x != 0, ""); // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. FMT_MSC_WARNING(suppress : 6102) return 31 ^ static_cast(r); } # define FMT_BUILTIN_CLZ(n) detail::clz(n) inline auto clzll(uint64_t x) -> int { unsigned long r = 0; # ifdef _WIN64 _BitScanReverse64(&r, x); # else // Scan the high 32 bits. if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ (r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. return 63 ^ static_cast(r); } # define FMT_BUILTIN_CLZLL(n) detail::clzll(n) inline auto ctz(uint32_t x) -> int { unsigned long r = 0; _BitScanForward(&r, x); FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. return static_cast(r); } # define FMT_BUILTIN_CTZ(n) detail::ctz(n) inline auto ctzll(uint64_t x) -> int { unsigned long r = 0; FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. # ifdef _WIN64 _BitScanForward64(&r, x); # else // Scan the low 32 bits. if (_BitScanForward(&r, static_cast(x))) return static_cast(r); // Scan the high 32 bits. _BitScanForward(&r, static_cast(x >> 32)); r += 32; # endif return static_cast(r); } # define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) # if !defined(__clang__) # pragma managed(pop) # endif } // namespace detail FMT_END_NAMESPACE #endif FMT_BEGIN_NAMESPACE namespace detail { #if __cplusplus >= 202002L || \ (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002) # define FMT_CONSTEXPR20 constexpr #else # define FMT_CONSTEXPR20 #endif // An equivalent of `*reinterpret_cast(&source)` that doesn't have // undefined behavior (e.g. due to type aliasing). // Example: uint64_t d = bit_cast(2.718); template inline auto bit_cast(const Source& source) -> Dest { static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); Dest dest; std::memcpy(&dest, &source, sizeof(dest)); return dest; } inline auto is_big_endian() -> bool { const auto u = 1u; struct bytes { char data[sizeof(u)]; }; return bit_cast(u).data[0] == 0; } // A fallback implementation of uintptr_t for systems that lack it. struct fallback_uintptr { unsigned char value[sizeof(void*)]; fallback_uintptr() = default; explicit fallback_uintptr(const void* p) { *this = bit_cast(p); if (is_big_endian()) { for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j) std::swap(value[i], value[j]); } } }; #ifdef UINTPTR_MAX using uintptr_t = ::uintptr_t; inline auto to_uintptr(const void* p) -> uintptr_t { return bit_cast(p); } #else using uintptr_t = fallback_uintptr; inline auto to_uintptr(const void* p) -> fallback_uintptr { return fallback_uintptr(p); } #endif // Returns the largest possible value for type T. Same as // std::numeric_limits::max() but shorter and not affected by the max macro. template constexpr auto max_value() -> T { return (std::numeric_limits::max)(); } template constexpr auto num_bits() -> int { return std::numeric_limits::digits; } // std::numeric_limits::digits may return 0 for 128-bit ints. template <> constexpr auto num_bits() -> int { return 128; } template <> constexpr auto num_bits() -> int { return 128; } template <> constexpr auto num_bits() -> int { return static_cast(sizeof(void*) * std::numeric_limits::digits); } FMT_INLINE void assume(bool condition) { (void)condition; #if FMT_HAS_BUILTIN(__builtin_assume) __builtin_assume(condition); #endif } // An approximation of iterator_t for pre-C++20 systems. template using iterator_t = decltype(std::begin(std::declval())); template using sentinel_t = decltype(std::end(std::declval())); // A workaround for std::string not having mutable data() until C++17. template inline auto get_data(std::basic_string& s) -> Char* { return &s[0]; } template inline auto get_data(Container& c) -> typename Container::value_type* { return c.data(); } #if defined(_SECURE_SCL) && _SECURE_SCL // Make a checked iterator to avoid MSVC warnings. template using checked_ptr = stdext::checked_array_iterator; template auto make_checked(T* p, size_t size) -> checked_ptr { return {p, size}; } #else template using checked_ptr = T*; template inline auto make_checked(T* p, size_t) -> T* { return p; } #endif // Attempts to reserve space for n extra characters in the output range. // Returns a pointer to the reserved range or a reference to it. template ::value)> #if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) #endif inline auto reserve(std::back_insert_iterator it, size_t n) -> checked_ptr { Container& c = get_container(it); size_t size = c.size(); c.resize(size + n); return make_checked(get_data(c) + size, n); } template inline auto reserve(buffer_appender it, size_t n) -> buffer_appender { buffer& buf = get_container(it); buf.try_reserve(buf.size() + n); return it; } template constexpr auto reserve(Iterator& it, size_t) -> Iterator& { return it; } template using reserve_iterator = remove_reference_t(), 0))>; template constexpr auto to_pointer(OutputIt, size_t) -> T* { return nullptr; } template auto to_pointer(buffer_appender it, size_t n) -> T* { buffer& buf = get_container(it); auto size = buf.size(); if (buf.capacity() < size + n) return nullptr; buf.try_resize(size + n); return buf.data() + size; } template ::value)> inline auto base_iterator(std::back_insert_iterator& it, checked_ptr) -> std::back_insert_iterator { return it; } template constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { return it; } // is spectacularly slow to compile in C++20 so use a simple fill_n // instead (#1998). template FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) -> OutputIt { for (Size i = 0; i < count; ++i) *out++ = value; return out; } template FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { if (is_constant_evaluated()) { return fill_n(out, count, value); } std::memset(out, value, to_unsigned(count)); return out + count; } #ifdef __cpp_char8_t using char8_type = char8_t; #else enum char8_type : unsigned char {}; #endif template FMT_CONSTEXPR FMT_NOINLINE auto copy_str_noinline(InputIt begin, InputIt end, OutputIt out) -> OutputIt { return copy_str(begin, end, out); } // A public domain branchless UTF-8 decoder by Christopher Wellons: // https://github.com/skeeto/branchless-utf8 /* Decode the next character, c, from s, reporting errors in e. * * Since this is a branchless decoder, four bytes will be read from the * buffer regardless of the actual length of the next character. This * means the buffer _must_ have at least three bytes of zero padding * following the end of the data stream. * * Errors are reported in e, which will be non-zero if the parsed * character was somehow invalid: invalid byte sequence, non-canonical * encoding, or a surrogate half. * * The function returns a pointer to the next character. When an error * occurs, this pointer will be a guess that depends on the particular * error, but it will always advance at least one byte. */ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) -> const char* { constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; constexpr const int shiftc[] = {0, 18, 12, 6, 0}; constexpr const int shifte[] = {0, 6, 4, 2, 0}; int len = code_point_length(s); const char* next = s + len; // Assume a four-byte character and load four bytes. Unused bits are // shifted out. *c = uint32_t(s[0] & masks[len]) << 18; *c |= uint32_t(s[1] & 0x3f) << 12; *c |= uint32_t(s[2] & 0x3f) << 6; *c |= uint32_t(s[3] & 0x3f) << 0; *c >>= shiftc[len]; // Accumulate the various error conditions. using uchar = unsigned char; *e = (*c < mins[len]) << 6; // non-canonical encoding *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? *e |= (*c > 0x10FFFF) << 8; // out of range? *e |= (uchar(s[1]) & 0xc0) >> 2; *e |= (uchar(s[2]) & 0xc0) >> 4; *e |= uchar(s[3]) >> 6; *e ^= 0x2a; // top two bits of each tail byte correct? *e >>= shifte[len]; return next; } template FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { auto decode = [f](const char* p) { auto cp = uint32_t(); auto error = 0; p = utf8_decode(p, &cp, &error); f(cp, error); return p; }; auto p = s.data(); const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. if (s.size() >= block_size) { for (auto end = p + s.size() - block_size + 1; p < end;) p = decode(p); } if (auto num_chars_left = s.data() + s.size() - p) { char buf[2 * block_size - 1] = {}; copy_str(p, p + num_chars_left, buf); p = buf; do { p = decode(p); } while (p - buf < num_chars_left); } } template inline auto compute_width(basic_string_view s) -> size_t { return s.size(); } // Computes approximate display width of a UTF-8 string. FMT_CONSTEXPR inline size_t compute_width(string_view s) { size_t num_code_points = 0; // It is not a lambda for compatibility with C++14. struct count_code_points { size_t* count; FMT_CONSTEXPR void operator()(uint32_t cp, int error) const { *count += detail::to_unsigned( 1 + (error == 0 && cp >= 0x1100 && (cp <= 0x115f || // Hangul Jamo init. consonants cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET〈 cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET 〉 // CJK ... Yi except Unicode Character “〿”: (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms (cp >= 0x20000 && cp <= 0x2fffd) || // CJK (cp >= 0x30000 && cp <= 0x3fffd) || // Miscellaneous Symbols and Pictographs + Emoticons: (cp >= 0x1f300 && cp <= 0x1f64f) || // Supplemental Symbols and Pictographs: (cp >= 0x1f900 && cp <= 0x1f9ff)))); } }; for_each_codepoint(s, count_code_points{&num_code_points}); return num_code_points; } inline auto compute_width(basic_string_view s) -> size_t { return compute_width(basic_string_view( reinterpret_cast(s.data()), s.size())); } template inline auto code_point_index(basic_string_view s, size_t n) -> size_t { size_t size = s.size(); return n < size ? n : size; } // Calculates the index of the nth code point in a UTF-8 string. inline auto code_point_index(basic_string_view s, size_t n) -> size_t { const char8_type* data = s.data(); size_t num_code_points = 0; for (size_t i = 0, size = s.size(); i != size; ++i) { if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i; } return s.size(); } template using is_fast_float = bool_constant::is_iec559 && sizeof(T) <= sizeof(double)>; #ifndef FMT_USE_FULL_CACHE_DRAGONBOX # define FMT_USE_FULL_CACHE_DRAGONBOX 0 #endif template template void buffer::append(const U* begin, const U* end) { while (begin != end) { auto count = to_unsigned(end - begin); try_reserve(size_ + count); auto free_cap = capacity_ - size_; if (free_cap < count) count = free_cap; std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count)); size_ += count; begin += count; } } template struct is_locale : std::false_type {}; template struct is_locale> : std::true_type {}; } // namespace detail FMT_MODULE_EXPORT_BEGIN // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. enum { inline_buffer_size = 500 }; /** \rst A dynamically growing memory buffer for trivially copyable/constructible types with the first ``SIZE`` elements stored in the object itself. You can use the ```memory_buffer`` type alias for ``char`` instead. **Example**:: fmt::memory_buffer out; format_to(out, "The answer is {}.", 42); This will append the following output to the ``out`` object: .. code-block:: none The answer is 42. The output can be converted to an ``std::string`` with ``to_string(out)``. \endrst */ template > class basic_memory_buffer final : public detail::buffer { private: T store_[SIZE]; // Don't inherit from Allocator avoid generating type_info for it. Allocator alloc_; // Deallocate memory allocated by the buffer. void deallocate() { T* data = this->data(); if (data != store_) alloc_.deallocate(data, this->capacity()); } protected: void grow(size_t size) final FMT_OVERRIDE; public: using value_type = T; using const_reference = const T&; explicit basic_memory_buffer(const Allocator& alloc = Allocator()) : alloc_(alloc) { this->set(store_, SIZE); } ~basic_memory_buffer() { deallocate(); } private: // Move data from other to this buffer. void move(basic_memory_buffer& other) { alloc_ = std::move(other.alloc_); T* data = other.data(); size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); std::uninitialized_copy(other.store_, other.store_ + size, detail::make_checked(store_, capacity)); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called // when deallocating. other.set(other.store_, 0); } this->resize(size); } public: /** \rst Constructs a :class:`fmt::basic_memory_buffer` object moving the content of the other object to it. \endrst */ basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); } /** \rst Moves the content of the other ``basic_memory_buffer`` object to this one. \endrst */ auto operator=(basic_memory_buffer&& other) FMT_NOEXCEPT -> basic_memory_buffer& { FMT_ASSERT(this != &other, ""); deallocate(); move(other); return *this; } // Returns a copy of the allocator associated with this buffer. auto get_allocator() const -> Allocator { return alloc_; } /** Resizes the buffer to contain *count* elements. If T is a POD type new elements may not be initialized. */ void resize(size_t count) { this->try_resize(count); } /** Increases the buffer capacity to *new_capacity*. */ void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } // Directly append data into the buffer using detail::buffer::append; template void append(const ContiguousRange& range) { append(range.data(), range.data() + range.size()); } }; template void basic_memory_buffer::grow(size_t size) { #ifdef FMT_FUZZ if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much"); #endif const size_t max_size = std::allocator_traits::max_size(alloc_); size_t old_capacity = this->capacity(); size_t new_capacity = old_capacity + old_capacity / 2; if (size > new_capacity) new_capacity = size; else if (new_capacity > max_size) new_capacity = size > max_size ? size : max_size; T* old_data = this->data(); T* new_data = std::allocator_traits::allocate(alloc_, new_capacity); // The following code doesn't throw, so the raw pointer above doesn't leak. std::uninitialized_copy(old_data, old_data + this->size(), detail::make_checked(new_data, new_capacity)); this->set(new_data, new_capacity); // deallocate must not throw according to the standard, but even if it does, // the buffer already uses the new storage and will deallocate it in // destructor. if (old_data != store_) alloc_.deallocate(old_data, old_capacity); } using memory_buffer = basic_memory_buffer; template struct is_contiguous> : std::true_type { }; namespace detail { FMT_API void print(std::FILE*, string_view); } /** A formatting error such as invalid format string. */ FMT_CLASS_API class FMT_API format_error : public std::runtime_error { public: explicit format_error(const char* message) : std::runtime_error(message) {} explicit format_error(const std::string& message) : std::runtime_error(message) {} format_error(const format_error&) = default; format_error& operator=(const format_error&) = default; format_error(format_error&&) = default; format_error& operator=(format_error&&) = default; ~format_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT; }; /** \rst Constructs a `~fmt::format_arg_store` object that contains references to arguments and can be implicitly converted to `~fmt::format_args`. If ``fmt`` is a compile-time string then `make_args_checked` checks its validity at compile time. \endrst */ template > FMT_INLINE auto make_args_checked(const S& fmt, const remove_reference_t&... args) -> format_arg_store, remove_reference_t...> { static_assert( detail::count<( std::is_base_of>::value && std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); detail::check_format_string(fmt); return {args...}; } // compile-time support namespace detail_exported { #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS template struct fixed_string { constexpr fixed_string(const Char (&str)[N]) { detail::copy_str(static_cast(str), str + N, data); } Char data[N]{}; }; #endif // Converts a compile-time string to basic_string_view. template constexpr auto compile_string_to_view(const Char (&s)[N]) -> basic_string_view { // Remove trailing NUL character if needed. Won't be present if this is used // with a raw character array (i.e. not defined as a string). return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; } template constexpr auto compile_string_to_view(detail::std_string_view s) -> basic_string_view { return {s.data(), s.size()}; } } // namespace detail_exported FMT_BEGIN_DETAIL_NAMESPACE inline void throw_format_error(const char* message) { FMT_THROW(format_error(message)); } template struct is_integral : std::is_integral {}; template <> struct is_integral : std::true_type {}; template <> struct is_integral : std::true_type {}; template using is_signed = std::integral_constant::is_signed || std::is_same::value>; // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. template ::value)> FMT_CONSTEXPR auto is_negative(T value) -> bool { return value < 0; } template ::value)> FMT_CONSTEXPR auto is_negative(T) -> bool { return false; } template ::value)> FMT_CONSTEXPR auto is_supported_floating_point(T) -> uint16_t { return (std::is_same::value && FMT_USE_FLOAT) || (std::is_same::value && FMT_USE_DOUBLE) || (std::is_same::value && FMT_USE_LONG_DOUBLE); } // Smallest of uint32_t, uint64_t, uint128_t that is large enough to // represent all values of an integral type T. template using uint32_or_64_or_128_t = conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, uint32_t, conditional_t() <= 64, uint64_t, uint128_t>>; template using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; #define FMT_POWERS_OF_10(factor) \ factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ (factor)*1000000, (factor)*10000000, (factor)*100000000, \ (factor)*1000000000 // Static data is placed in this class template for the header-only config. template struct basic_data { // log10(2) = 0x0.4d104d427de7fbcc... static const uint64_t log10_2_significand = 0x4d104d427de7fbcc; // GCC generates slightly better code for pairs than chars. FMT_API static constexpr const char digits[][2] = { {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; FMT_API static constexpr const char hex_digits[] = "0123456789abcdef"; FMT_API static constexpr const char signs[] = {0, '-', '+', ' '}; FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '}; FMT_API static constexpr const char left_padding_shifts[] = {31, 31, 0, 1, 0}; FMT_API static constexpr const char right_padding_shifts[] = {0, 31, 0, 1, 0}; }; #ifdef FMT_SHARED // Required for -flto, -fivisibility=hidden and -shared to work extern template struct basic_data; #endif // This is a struct rather than an alias to avoid shadowing warnings in gcc. struct data : basic_data<> {}; template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { int count = 1; for (;;) { // Integer division is slow so do it for a group of four digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. if (n < 10) return count; if (n < 100) return count + 1; if (n < 1000) return count + 2; if (n < 10000) return count + 3; n /= 10000u; count += 4; } } #if FMT_USE_INT128 FMT_CONSTEXPR inline auto count_digits(uint128_t n) -> int { return count_digits_fallback(n); } #endif // Returns the number of decimal digits in n. Leading zeros are not counted // except for n == 0 in which case count_digits returns 1. FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { #ifdef FMT_BUILTIN_CLZLL if (!is_constant_evaluated()) { // https://github.com/fmtlib/format-benchmark/blob/master/digits10 // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). constexpr uint16_t bsr2log10[] = { 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; constexpr const uint64_t zero_or_powers_of_10[] = { 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), 10000000000000000000ULL}; return t - (n < zero_or_powers_of_10[t]); } #endif return count_digits_fallback(n); } // Counts the number of digits in n. BITS = log2(radix). template FMT_CONSTEXPR auto count_digits(UInt n) -> int { #ifdef FMT_BUILTIN_CLZ if (num_bits() == 32) return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; #endif int num_digits = 0; do { ++num_digits; } while ((n >>= BITS) != 0); return num_digits; } template <> auto count_digits<4>(detail::fallback_uintptr n) -> int; // It is a separate function rather than a part of count_digits to workaround // the lack of static constexpr in constexpr functions. FMT_INLINE uint64_t count_digits_inc(int n) { // An optimization by Kendall Willets from https://bit.ly/3uOIQrB. // This increments the upper 32 bits (log10(T) - 1) when >= T is added. #define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) static constexpr uint64_t table[] = { FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M FMT_INC(1000000000), FMT_INC(1000000000) // 4B }; return table[n]; } // Optional version of count_digits for better performance on 32-bit platforms. FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { #ifdef FMT_BUILTIN_CLZ if (!is_constant_evaluated()) { auto inc = count_digits_inc(FMT_BUILTIN_CLZ(n | 1) ^ 31); return static_cast((n + inc) >> 32); } #endif return count_digits_fallback(n); } template constexpr auto digits10() FMT_NOEXCEPT -> int { return std::numeric_limits::digits10; } template <> constexpr auto digits10() FMT_NOEXCEPT -> int { return 38; } template <> constexpr auto digits10() FMT_NOEXCEPT -> int { return 38; } template struct thousands_sep_result { std::string grouping; Char thousands_sep; }; template FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; template inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { auto result = thousands_sep_impl(loc); return {result.grouping, Char(result.thousands_sep)}; } template <> inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { return thousands_sep_impl(loc); } template FMT_API auto decimal_point_impl(locale_ref loc) -> Char; template inline auto decimal_point(locale_ref loc) -> Char { return Char(decimal_point_impl(loc)); } template <> inline auto decimal_point(locale_ref loc) -> wchar_t { return decimal_point_impl(loc); } // Compares two characters for equality. template auto equal2(const Char* lhs, const char* rhs) -> bool { return lhs[0] == rhs[0] && lhs[1] == rhs[1]; } inline auto equal2(const char* lhs, const char* rhs) -> bool { return memcmp(lhs, rhs, 2) == 0; } // Copies two characters from src to dst. template void copy2(Char* dst, const char* src) { *dst++ = static_cast(*src++); *dst = static_cast(*src); } FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } template struct format_decimal_result { Iterator begin; Iterator end; }; // Formats a decimal unsigned integer value writing into out pointing to a // buffer of specified size. The caller must ensure that the buffer is large // enough. template FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) -> format_decimal_result { FMT_ASSERT(size >= count_digits(value), "invalid digit count"); out += size; Char* end = out; if (is_constant_evaluated()) { while (value >= 10) { *--out = static_cast('0' + value % 10); value /= 10; } *--out = static_cast('0' + value); return {out, end}; } while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. out -= 2; copy2(out, data::digits[value % 100]); value /= 100; } if (value < 10) { *--out = static_cast('0' + value); return {out, end}; } out -= 2; copy2(out, data::digits[value]); return {out, end}; } template >::value)> inline auto format_decimal(Iterator out, UInt value, int size) -> format_decimal_result { // Buffer is large enough to hold all digits (digits10 + 1). Char buffer[digits10() + 1]; auto end = format_decimal(buffer, value, size).end; return {out, detail::copy_str_noinline(buffer, end, out)}; } template FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, bool upper = false) -> Char* { buffer += num_digits; Char* end = buffer; do { const char* digits = upper ? "0123456789ABCDEF" : data::hex_digits; unsigned digit = (value & ((1 << BASE_BITS) - 1)); *--buffer = static_cast(BASE_BITS < 4 ? static_cast('0' + digit) : digits[digit]); } while ((value >>= BASE_BITS) != 0); return end; } template auto format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, bool = false) -> Char* { auto char_digits = std::numeric_limits::digits / 4; int start = (num_digits + char_digits - 1) / char_digits - 1; if (int start_digits = num_digits % char_digits) { unsigned value = n.value[start--]; buffer = format_uint(buffer, value, start_digits); } for (; start >= 0; --start) { unsigned value = n.value[start]; buffer += char_digits; auto p = buffer; for (int i = 0; i < char_digits; ++i) { unsigned digit = (value & ((1 << BASE_BITS) - 1)); *--p = static_cast(data::hex_digits[digit]); value >>= BASE_BITS; } } return buffer; } template inline auto format_uint(It out, UInt value, int num_digits, bool upper = false) -> It { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { format_uint(ptr, value, num_digits, upper); return out; } // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). char buffer[num_bits() / BASE_BITS + 1]; format_uint(buffer, value, num_digits, upper); return detail::copy_str_noinline(buffer, buffer + num_digits, out); } // A converter from UTF-8 to UTF-16. class utf8_to_utf16 { private: basic_memory_buffer buffer_; public: FMT_API explicit utf8_to_utf16(string_view s); operator basic_string_view() const { return {&buffer_[0], size()}; } auto size() const -> size_t { return buffer_.size() - 1; } auto c_str() const -> const wchar_t* { return &buffer_[0]; } auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; namespace dragonbox { // Type-specific information that Dragonbox uses. template struct float_info; template <> struct float_info { using carrier_uint = uint32_t; static const int significand_bits = 23; static const int exponent_bits = 8; static const int min_exponent = -126; static const int max_exponent = 127; static const int exponent_bias = -127; static const int decimal_digits = 9; static const int kappa = 1; static const int big_divisor = 100; static const int small_divisor = 10; static const int min_k = -31; static const int max_k = 46; static const int cache_bits = 64; static const int divisibility_check_by_5_threshold = 39; static const int case_fc_pm_half_lower_threshold = -1; static const int case_fc_pm_half_upper_threshold = 6; static const int case_fc_lower_threshold = -2; static const int case_fc_upper_threshold = 6; static const int case_shorter_interval_left_endpoint_lower_threshold = 2; static const int case_shorter_interval_left_endpoint_upper_threshold = 3; static const int shorter_interval_tie_lower_threshold = -35; static const int shorter_interval_tie_upper_threshold = -35; static const int max_trailing_zeros = 7; }; template <> struct float_info { using carrier_uint = uint64_t; static const int significand_bits = 52; static const int exponent_bits = 11; static const int min_exponent = -1022; static const int max_exponent = 1023; static const int exponent_bias = -1023; static const int decimal_digits = 17; static const int kappa = 2; static const int big_divisor = 1000; static const int small_divisor = 100; static const int min_k = -292; static const int max_k = 326; static const int cache_bits = 128; static const int divisibility_check_by_5_threshold = 86; static const int case_fc_pm_half_lower_threshold = -2; static const int case_fc_pm_half_upper_threshold = 9; static const int case_fc_lower_threshold = -4; static const int case_fc_upper_threshold = 9; static const int case_shorter_interval_left_endpoint_lower_threshold = 2; static const int case_shorter_interval_left_endpoint_upper_threshold = 3; static const int shorter_interval_tie_lower_threshold = -77; static const int shorter_interval_tie_upper_threshold = -77; static const int max_trailing_zeros = 16; }; template struct decimal_fp { using significand_type = typename float_info::carrier_uint; significand_type significand; int exponent; }; template FMT_API auto to_decimal(T x) FMT_NOEXCEPT -> decimal_fp; } // namespace dragonbox template constexpr auto exponent_mask() -> typename dragonbox::float_info::carrier_uint { using uint = typename dragonbox::float_info::carrier_uint; return ((uint(1) << dragonbox::float_info::exponent_bits) - 1) << dragonbox::float_info::significand_bits; } // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. template auto write_exponent(int exp, It it) -> It { FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); if (exp < 0) { *it++ = static_cast('-'); exp = -exp; } else { *it++ = static_cast('+'); } if (exp >= 100) { const char* top = data::digits[exp / 100]; if (exp >= 1000) *it++ = static_cast(top[0]); *it++ = static_cast(top[1]); exp %= 100; } const char* d = data::digits[exp]; *it++ = static_cast(d[0]); *it++ = static_cast(d[1]); return it; } template auto format_float(T value, int precision, float_specs specs, buffer& buf) -> int; // Formats a floating-point number with snprintf. template auto snprintf_float(T value, int precision, float_specs specs, buffer& buf) -> int; template auto promote_float(T value) -> T { return value; } inline auto promote_float(float value) -> double { return static_cast(value); } template FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, const fill_t& fill) -> OutputIt { auto fill_size = fill.size(); if (fill_size == 1) return detail::fill_n(it, n, fill[0]); auto data = fill.data(); for (size_t i = 0; i < n; ++i) it = copy_str(data, data + fill_size, it); return it; } // Writes the output of f, padded according to format specifications in specs. // size: output size in code units. // width: output display width in (terminal) column positions. template FMT_CONSTEXPR auto write_padded(OutputIt out, const basic_format_specs& specs, size_t size, size_t width, F&& f) -> OutputIt { static_assert(align == align::left || align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); size_t padding = spec_width > width ? spec_width - width : 0; auto* shifts = align == align::left ? data::left_padding_shifts : data::right_padding_shifts; size_t left_padding = padding >> shifts[specs.align]; size_t right_padding = padding - left_padding; auto it = reserve(out, size + padding * specs.fill.size()); if (left_padding != 0) it = fill(it, left_padding, specs.fill); it = f(it); if (right_padding != 0) it = fill(it, right_padding, specs.fill); return base_iterator(out, it); } template constexpr auto write_padded(OutputIt out, const basic_format_specs& specs, size_t size, F&& f) -> OutputIt { return write_padded(out, specs, size, size, f); } template FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, const basic_format_specs& specs) -> OutputIt { return write_padded( out, specs, bytes.size(), [bytes](reserve_iterator it) { const char* data = bytes.data(); return copy_str(data, data + bytes.size(), it); }); } template auto write_ptr(OutputIt out, UIntPtr value, const basic_format_specs* specs) -> OutputIt { int num_digits = count_digits<4>(value); auto size = to_unsigned(num_digits) + size_t(2); auto write = [=](reserve_iterator it) { *it++ = static_cast('0'); *it++ = static_cast('x'); return format_uint<4, Char>(it, value, num_digits); }; return specs ? write_padded(out, *specs, size, write) : base_iterator(out, write(reserve(out, size))); } template FMT_CONSTEXPR auto write_char(OutputIt out, Char value, const basic_format_specs& specs) -> OutputIt { return write_padded(out, specs, 1, [=](reserve_iterator it) { *it++ = value; return it; }); } template FMT_CONSTEXPR auto write(OutputIt out, Char value, const basic_format_specs& specs, locale_ref loc = {}) -> OutputIt { return check_char_specs(specs) ? write_char(out, value, specs) : write(out, static_cast(value), specs, loc); } // Data for write_int that doesn't depend on output iterator type. It is used to // avoid template code bloat. template struct write_int_data { size_t size; size_t padding; FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, const basic_format_specs& specs) : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { auto width = to_unsigned(specs.width); if (width > size) { padding = width - size; size = width; } } else if (specs.precision > num_digits) { size = (prefix >> 24) + to_unsigned(specs.precision); padding = to_unsigned(specs.precision - num_digits); } } }; // Writes an integer in the format // // where are written by write_digits(it). // prefix contains chars in three lower bytes and the size in the fourth byte. template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, unsigned prefix, const basic_format_specs& specs, W write_digits) -> OutputIt { // Slightly faster check for specs.width == 0 && specs.precision == -1. if ((specs.width | (specs.precision + 1)) == 0) { auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); if (prefix != 0) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); } return base_iterator(out, write_digits(it)); } auto data = write_int_data(num_digits, prefix, specs); return write_padded( out, specs, data.size, [=](reserve_iterator it) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); it = detail::fill_n(it, data.padding, static_cast('0')); return write_digits(it); }); } template auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, const basic_format_specs& specs, locale_ref loc) -> bool { static_assert(std::is_same, UInt>::value, ""); const auto sep_size = 1; auto ts = thousands_sep(loc); if (!ts.thousands_sep) return false; int num_digits = count_digits(value); int size = num_digits, n = num_digits; const std::string& groups = ts.grouping; std::string::const_iterator group = groups.cbegin(); while (group != groups.cend() && n > *group && *group > 0 && *group != max_value()) { size += sep_size; n -= *group; ++group; } if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); char digits[40]; format_decimal(digits, value, num_digits); basic_memory_buffer buffer; if (prefix != 0) ++size; const auto usize = to_unsigned(size); buffer.resize(usize); basic_string_view s(&ts.thousands_sep, sep_size); // Index of a decimal digit with the least significant digit having index 0. int digit_index = 0; group = groups.cbegin(); auto p = buffer.data() + size - 1; for (int i = num_digits - 1; i > 0; --i) { *p-- = static_cast(digits[i]); if (*group <= 0 || ++digit_index % *group != 0 || *group == max_value()) continue; if (group + 1 != groups.cend()) { digit_index = 0; ++group; } std::uninitialized_copy(s.data(), s.data() + s.size(), make_checked(p, s.size())); p -= s.size(); } *p-- = static_cast(*digits); if (prefix != 0) *p = static_cast(prefix); auto data = buffer.data(); out = write_padded( out, specs, usize, usize, [=](reserve_iterator it) { return copy_str(data, data + size, it); }); return true; } FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { prefix |= prefix != 0 ? value << 8 : value; prefix += (1u + (value > 0xff ? 1 : 0)) << 24; } template struct write_int_arg { UInt abs_value; unsigned prefix; }; template FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) -> write_int_arg> { auto prefix = 0u; auto abs_value = static_cast>(value); if (is_negative(value)) { prefix = 0x01000000 | '-'; abs_value = 0 - abs_value; } else { prefix = data::prefixes[sign]; } return {abs_value, prefix}; } template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, const basic_format_specs& specs, locale_ref loc) -> OutputIt { static_assert(std::is_same>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; auto utype = static_cast(specs.type); switch (specs.type) { case 0: case 'd': { if (specs.localized && write_int_localized(out, static_cast>(abs_value), prefix, specs, loc)) { return out; } auto num_digits = count_digits(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_decimal(it, abs_value, num_digits).end; }); } case 'x': case 'X': { if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); bool upper = specs.type != 'x'; int num_digits = count_digits<4>(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_uint<4, Char>(it, abs_value, num_digits, upper); }); } case 'b': case 'B': { if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); int num_digits = count_digits<1>(abs_value); return write_int(out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_uint<1, Char>(it, abs_value, num_digits); }); } case 'o': { int num_digits = count_digits<3>(abs_value); if (specs.alt && specs.precision <= num_digits && abs_value != 0) { // Octal prefix '0' is counted as a digit, so only add it if precision // is not greater than the number of digits. prefix_append(prefix, '0'); } return write_int(out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_uint<3, Char>(it, abs_value, num_digits); }); } case 'c': return write_char(out, static_cast(abs_value), specs); default: FMT_THROW(format_error("invalid type specifier")); } return out; } template ::value && !std::is_same::value && std::is_same>::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, const basic_format_specs& specs, locale_ref loc) -> OutputIt { return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); } // An inlined version of write used in format string compilation. template ::value && !std::is_same::value && !std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, const basic_format_specs& specs, locale_ref loc) -> OutputIt { return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, const basic_format_specs& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) size = code_point_index(s, to_unsigned(specs.precision)); auto width = specs.width != 0 ? compute_width(basic_string_view(data, size)) : 0; return write_padded(out, specs, size, width, [=](reserve_iterator it) { return copy_str(data, data + size, it); }); } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view> s, const basic_format_specs& specs, locale_ref) -> OutputIt { return write(out, s, specs); } template FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const basic_format_specs& specs, locale_ref) -> OutputIt { return check_cstring_type_spec(specs.type) ? write(out, basic_string_view(s), specs, {}) : write_ptr(out, to_uintptr(s), &specs); } template auto write_nonfinite(OutputIt out, bool isinf, basic_format_specs specs, const float_specs& fspecs) -> OutputIt { auto str = isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); constexpr size_t str_size = 3; auto sign = fspecs.sign; auto size = str_size + (sign ? 1 : 0); // Replace '0'-padding with space for non-finite values. const bool is_zero_fill = specs.fill.size() == 1 && *specs.fill.data() == static_cast('0'); if (is_zero_fill) specs.fill[0] = static_cast(' '); return write_padded(out, specs, size, [=](reserve_iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); return copy_str(str, str + str_size, it); }); } // A decimal floating-point number significand * pow(10, exp). struct big_decimal_fp { const char* significand; int significand_size; int exponent; }; inline auto get_significand_size(const big_decimal_fp& fp) -> int { return fp.significand_size; } template inline auto get_significand_size(const dragonbox::decimal_fp& fp) -> int { return count_digits(fp.significand); } template inline auto write_significand(OutputIt out, const char* significand, int& significand_size) -> OutputIt { return copy_str(significand, significand + significand_size, out); } template inline auto write_significand(OutputIt out, UInt significand, int significand_size) -> OutputIt { return format_decimal(out, significand, significand_size).end; } template ::value)> inline auto write_significand(Char* out, UInt significand, int significand_size, int integral_size, Char decimal_point) -> Char* { if (!decimal_point) return format_decimal(out, significand, significand_size).end; auto end = format_decimal(out + 1, significand, significand_size).end; if (integral_size == 1) { out[0] = out[1]; } else { std::uninitialized_copy_n(out + 1, integral_size, make_checked(out, to_unsigned(integral_size))); } out[integral_size] = decimal_point; return end; } template >::value)> inline auto write_significand(OutputIt out, UInt significand, int significand_size, int integral_size, Char decimal_point) -> OutputIt { // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. Char buffer[digits10() + 2]; auto end = write_significand(buffer, significand, significand_size, integral_size, decimal_point); return detail::copy_str_noinline(buffer, end, out); } template inline auto write_significand(OutputIt out, const char* significand, int significand_size, int integral_size, Char decimal_point) -> OutputIt { out = detail::copy_str_noinline(significand, significand + integral_size, out); if (!decimal_point) return out; *out++ = decimal_point; return detail::copy_str_noinline(significand + integral_size, significand + significand_size, out); } template auto write_float(OutputIt out, const DecimalFP& fp, const basic_format_specs& specs, float_specs fspecs, Char decimal_point) -> OutputIt { auto significand = fp.significand; int significand_size = get_significand_size(fp); static const Char zero = static_cast('0'); auto sign = fspecs.sign; size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); using iterator = reserve_iterator; int output_exp = fp.exponent + significand_size - 1; auto use_exp_format = [=]() { if (fspecs.format == float_format::exp) return true; if (fspecs.format != float_format::general) return false; // Use the fixed notation if the exponent is in [exp_lower, exp_upper), // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. const int exp_lower = -4, exp_upper = 16; return output_exp < exp_lower || output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper); }; if (use_exp_format()) { int num_zeros = 0; if (fspecs.showpoint) { num_zeros = fspecs.precision - significand_size; if (num_zeros < 0) num_zeros = 0; size += to_unsigned(num_zeros); } else if (significand_size == 1) { decimal_point = Char(); } auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; int exp_digits = 2; if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); char exp_char = fspecs.upper ? 'E' : 'e'; auto write = [=](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); // Insert a decimal point after the first digit and add an exponent. it = write_significand(it, significand, significand_size, 1, decimal_point); if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); *it++ = static_cast(exp_char); return write_exponent(output_exp, it); }; return specs.width > 0 ? write_padded(out, specs, size, write) : base_iterator(out, write(reserve(out, size))); } int exp = fp.exponent + significand_size; if (fp.exponent >= 0) { // 1234e5 -> 123400000[.0+] size += to_unsigned(fp.exponent); int num_zeros = fspecs.precision - exp; #ifdef FMT_FUZZ if (num_zeros > 5000) throw std::runtime_error("fuzz mode - avoiding excessive cpu use"); #endif if (fspecs.showpoint) { if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; if (num_zeros > 0) size += to_unsigned(num_zeros) + 1; } return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); it = write_significand(it, significand, significand_size); it = detail::fill_n(it, fp.exponent, zero); if (!fspecs.showpoint) return it; *it++ = decimal_point; return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } else if (exp > 0) { // 1234e-2 -> 12.34[0+] int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); it = write_significand(it, significand, significand_size, exp, decimal_point); return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } // 1234e-6 -> 0.001234 int num_zeros = -exp; if (significand_size == 0 && fspecs.precision >= 0 && fspecs.precision < num_zeros) { num_zeros = fspecs.precision; } bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint; size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); *it++ = zero; if (!pointy) return it; *it++ = decimal_point; it = detail::fill_n(it, num_zeros, zero); return write_significand(it, significand, significand_size); }); } template ::value)> auto write(OutputIt out, T value, basic_format_specs specs, locale_ref loc = {}) -> OutputIt { if (const_check(!is_supported_floating_point(value))) return out; float_specs fspecs = parse_float_type_spec(specs); fspecs.sign = specs.sign; if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. fspecs.sign = sign::minus; value = -value; } else if (fspecs.sign == sign::minus) { fspecs.sign = sign::none; } if (!std::isfinite(value)) return write_nonfinite(out, std::isinf(value), specs, fspecs); if (specs.align == align::numeric && fspecs.sign) { auto it = reserve(out, 1); *it++ = static_cast(data::signs[fspecs.sign]); out = base_iterator(out, it); fspecs.sign = sign::none; if (specs.width != 0) --specs.width; } memory_buffer buffer; if (fspecs.format == float_format::hex) { if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); snprintf_float(promote_float(value), specs.precision, fspecs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; if (fspecs.format == float_format::exp) { if (precision == max_value()) FMT_THROW(format_error("number is too big")); else ++precision; } if (const_check(std::is_same())) fspecs.binary32 = true; fspecs.use_grisu = is_fast_float(); int exp = format_float(promote_float(value), precision, fspecs, buffer); fspecs.precision = precision; Char point = fspecs.locale ? decimal_point(loc) : static_cast('.'); auto fp = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; return write_float(out, fp, specs, fspecs, point); } template ::value)> auto write(OutputIt out, T value) -> OutputIt { if (const_check(!is_supported_floating_point(value))) return out; using floaty = conditional_t::value, double, T>; using uint = typename dragonbox::float_info::carrier_uint; auto bits = bit_cast(value); auto fspecs = float_specs(); auto sign_bit = bits & (uint(1) << (num_bits() - 1)); if (sign_bit != 0) { fspecs.sign = sign::minus; value = -value; } static const auto specs = basic_format_specs(); uint mask = exponent_mask(); if ((bits & mask) == mask) return write_nonfinite(out, std::isinf(value), specs, fspecs); auto dec = dragonbox::to_decimal(static_cast(value)); return write_float(out, dec, specs, fspecs, static_cast('.')); } template ::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { return write(out, value, basic_format_specs()); } template auto write(OutputIt out, monostate, basic_format_specs = {}, locale_ref = {}) -> OutputIt { FMT_ASSERT(false, ""); return out; } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) -> OutputIt { auto it = reserve(out, value.size()); it = copy_str_noinline(value.begin(), value.end(), it); return base_iterator(out, it); } template ::value)> constexpr auto write(OutputIt out, const T& value) -> OutputIt { return write(out, to_string_view(value)); } template ::value && !std::is_same::value && !std::is_same::value)> FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { auto abs_value = static_cast>(value); bool negative = is_negative(value); // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. if (negative) abs_value = ~abs_value + 1; int num_digits = count_digits(abs_value); auto size = (negative ? 1 : 0) + static_cast(num_digits); auto it = reserve(out, size); if (auto ptr = to_pointer(it, size)) { if (negative) *ptr++ = static_cast('-'); format_decimal(ptr, abs_value, num_digits); return out; } if (negative) *it++ = static_cast('-'); it = format_decimal(it, abs_value, num_digits).end; return base_iterator(out, it); } // FMT_ENABLE_IF() condition separated to workaround MSVC bug template < typename Char, typename OutputIt, typename T, bool check = std::is_enum::value && !std::is_same::value && mapped_type_constant>::value != type::custom_type, FMT_ENABLE_IF(check)> FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { return write( out, static_cast::type>(value)); } template ::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, const basic_format_specs& specs = {}, locale_ref = {}) -> OutputIt { return specs.type && specs.type != 's' ? write(out, value ? 1 : 0, specs, {}) : write_bytes(out, value ? "true" : "false", specs); } template FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { auto it = reserve(out, 1); *it++ = value; return base_iterator(out, it); } template FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) -> OutputIt { if (!value) { FMT_THROW(format_error("string pointer is null")); } else { auto length = std::char_traits::length(value); out = write(out, basic_string_view(value, length)); } return out; } template ::value)> auto write(OutputIt out, const T* value, const basic_format_specs& specs = {}, locale_ref = {}) -> OutputIt { check_pointer_type_spec(specs.type, error_handler()); return write_ptr(out, to_uintptr(value), &specs); } template FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> typename std::enable_if< mapped_type_constant>::value == type::custom_type, OutputIt>::type { using context_type = basic_format_context; using formatter_type = conditional_t::value, typename context_type::template formatter_type, fallback_formatter>; context_type ctx(out, {}, {}); return formatter_type().format(value, ctx); } // An argument visitor that formats the argument and writes it via the output // iterator. It's a class and not a generic lambda for compatibility with C++11. template struct default_arg_formatter { using iterator = buffer_appender; using context = buffer_context; iterator out; basic_format_args args; locale_ref loc; template auto operator()(T value) -> iterator { return write(out, value); } auto operator()(typename basic_format_arg::handle h) -> iterator { basic_format_parse_context parse_ctx({}); context format_ctx(out, args, loc); h.format(parse_ctx, format_ctx); return format_ctx.out(); } }; template struct arg_formatter { using iterator = buffer_appender; using context = buffer_context; iterator out; const basic_format_specs& specs; locale_ref locale; template FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator { return detail::write(out, value, specs, locale); } auto operator()(typename basic_format_arg::handle) -> iterator { // User-defined types are handled separately because they require access // to the parse context. return out; } }; template struct custom_formatter { basic_format_parse_context& parse_ctx; buffer_context& ctx; void operator()( typename basic_format_arg>::handle h) const { h.format(parse_ctx, ctx); } template void operator()(T) const {} }; template using is_integer = bool_constant::value && !std::is_same::value && !std::is_same::value && !std::is_same::value>; template class width_checker { public: explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { if (is_negative(value)) handler_.on_error("negative width"); return static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { handler_.on_error("width is not integer"); return 0; } private: ErrorHandler& handler_; }; template class precision_checker { public: explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { if (is_negative(value)) handler_.on_error("negative precision"); return static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { handler_.on_error("precision is not integer"); return 0; } private: ErrorHandler& handler_; }; template